# # 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 /* * 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 */ /* * 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 #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 /* Debug flags */ #define D_ALL 0xffff /* enable all debug */ #define D_PROBE 0x0001 /* probe mechanism */ #define D_FAILREP 0x0002 /* failure/repair mechanism */ #define D_PHYINT 0x0004 /* phyint table */ #define D_LOGINT 0x0008 /* logint table */ #define D_TARGET 0x0010 /* target table */ #define D_TIMER 0x0020 /* Timer mechanism */ #define D_PKTBAD 0x0040 /* Malformed packet */ #define D_LINKNOTE 0x0080 /* Link up/down notifications */ /* * Need a common header file that defines the 2 constants below. * Many applications need them. */ #define IF_SEPARATOR ':' #define IPV6_MAX_HOPS 255 /* * General parameters for phyint failure/repair detection */ #define NUM_PROBE_FAILS 5 /* NUM_PROBE_FAILS probe failures */ /* trigger NIC failure detection */ #define NUM_PROBE_REPAIRS 10 /* NUM_PROBE_REPAIRS probe repairs */ /* trigger NIC repair detection */ #define MIN_RANDOM_FACTOR 0.5 /* Randomization factors to */ #define MAX_RANDOM_FACTOR 1.0 /* determine probe send time */ #define MIN_PROBE_TARGETS 3 /* Minimum number of targets */ #define MAX_PROBE_TARGETS 5 /* Maximum number of targets */ /* * A target that is declared slow is usable again after MIN_RECOVERY_TIME ns */ #define MIN_RECOVERY_TIME (60000000000LL) /* (In ns) 60 secs */ /* * If the Failure Detection Time (FDT) is bumped up because the target CRTT * is high, it won't be reduced for the next MIN_SETTLING_TIME ns, to prevent * flapping of FDT */ #define MIN_SETTLING_TIME (60000000000LL) /* (In ns) 60 secs */ /* * An admin or script might place a phyint in a group before assigning a test * address. To give them time to configure a test address, we delay whining * about it being missing for TESTADDR_CONF_TIME seconds. */ #define TESTADDR_CONF_TIME 20 /* * The circular probe stats array should be able to hold enough * samples to detect phyint failure, target failure, phyint repair * and target repair. */ #define PROBE_STATS_COUNT \ ((uint16_t)(NUM_PROBE_REPAIRS * MAX_PROBE_TARGETS + 2)) #define FAILURE_DETECTION_TIME 10000 /* Default is 10 s */ #define MIN_FAILURE_DETECTION_TIME 100 /* Minimum is 100 ms */ #define FAILURE_DETECTION_QP 40 /* quiet period, in seconds */ #define NEXT_FDT_MULTIPLE 2 /* Raise or lower the FDT by this */ /* factor when required */ #define LOWER_FDT_TRIGGER 4 /* Lower the FDT if crtt is less */ /* than FDT / LOWER_FDT_TRIGGER */ #define EXCEPTION_FACTOR 2 /* The exception target has a crtt */ /* greater by this factor */ #define IF_SCAN_INTERVAL 20000 /* Do initifs() every 20 secs */ /* Return a random number from a range inclusive of the endpoints */ #define GET_RANDOM(LOW, HIGH) (random() % ((HIGH) - (LOW) + 1) + (LOW)) #define TIMER_INFINITY 0x7FFFFFFFU /* Never time out */ /* * Comparing unsigned 32 bit time values in a circular 32-bit sequence space */ #define TIME_GE(a, b) ((int32_t)((a) - (b)) >= 0) #define TIME_GT(a, b) ((int32_t)((a) - (b)) > 0) #define TIME_LT(a, b) ((int32_t)((a) - (b)) < 0) #define TIME_LE(a, b) ((int32_t)((a) - (b)) <= 0) /* * Comparing unsigned 16 bit sequence numbers in a circular 16-bit * sequence space */ #define SEQ_GE(a, b) ((int16_t)((a) - (b)) >= (int16_t)0) #define SEQ_GT(a, b) ((int16_t)((a) - (b)) > (int16_t)0) #define SEQ_LT(a, b) ((int16_t)((a) - (b)) < (int16_t)0) #define SEQ_LE(a, b) ((int16_t)((a) - (b)) <= (int16_t)0) #define AF_OTHER(af) ((af) == AF_INET ? AF_INET6 : AF_INET) #define AF_STR(af) ((af) == AF_INET ? "inet" : "inet6") /* * Globals */ extern boolean_t failback_enabled; /* cmd option to disable failbacks */ extern boolean_t track_all_phyints; /* cmd option to track all phyints */ /* all times below in millisec */ extern int user_probe_interval; /* interval between probes, as */ /* derived from user specified fdt */ extern int user_failure_detection_time; /* User specified fdt */ extern int ifsock_v4; /* IPv4 socket for ioctls */ extern int ifsock_v6; /* IPv6 socket for ioctls */ extern int debug; /* debug option */ extern boolean_t cleanup_started; /* true if we're shutting down */ extern boolean_t handle_link_notifications; /* * Function prototypes */ extern void timer_schedule(uint_t delay); extern void logmsg(int pri, const char *fmt, ...); extern void logperror(const char *str); extern int poll_add(int fd); extern int poll_remove(int fd); extern uint64_t getcurrentsec(void); extern uint_t getcurrenttime(void); #define logerr(...) logmsg(LOG_ERR, __VA_ARGS__) #define logtrace(...) logmsg(LOG_INFO, __VA_ARGS__) #define logdebug(...) logmsg(LOG_DEBUG, __VA_ARGS__) #ifdef __cplusplus } #endif #endif /* _MPD_DEFS_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. * * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #include "mpd_defs.h" #include "mpd_tables.h" int debug = 0; /* Debug flag */ static int pollfd_num = 0; /* Num. of poll descriptors */ static struct pollfd *pollfds = NULL; /* Array of poll descriptors */ /* All times below in ms */ int user_failure_detection_time; /* user specified failure detection */ /* time (fdt) */ int user_probe_interval; /* derived from user specified fdt */ /* * Structure to store mib2 information returned by the kernel. * This is used to process routing table information. */ typedef struct mib_item_s { struct mib_item_s *mi_next; struct opthdr mi_opthdr; void *mi_valp; } mib_item_t; static int rtsock_v4; /* AF_INET routing socket */ static int rtsock_v6; /* AF_INET6 routing socket */ int ifsock_v4 = -1; /* IPv4 socket for ioctls */ int ifsock_v6 = -1; /* IPv6 socket for ioctls */ static int lsock_v4; /* Listen socket to detect mpathd */ static int lsock_v6; /* Listen socket to detect mpathd */ static int mibfd = -1; /* fd to get mib info */ static boolean_t force_mcast = _B_FALSE; /* Only for test purposes */ static uint_t last_initifs_time; /* Time when initifs was last run */ static char **argv0; /* Saved for re-exec on SIGHUP */ boolean_t handle_link_notifications = _B_TRUE; static int ipRouteEntrySize; /* Size of IPv4 route entry */ static int ipv6RouteEntrySize; /* Size of IPv6 route entry */ static void initlog(void); static void run_timeouts(void); static void initifs(void); static void check_if_removed(struct phyint_instance *pii); static void select_test_ifs(void); static void update_router_list(mib_item_t *item); static void mib_get_constants(mib_item_t *item); static int mibwalk(void (*proc)(mib_item_t *)); static void ire_process_v4(mib2_ipRouteEntry_t *buf, size_t len); static void ire_process_v6(mib2_ipv6RouteEntry_t *buf, size_t len); static void router_add_common(int af, char *ifname, struct in6_addr nexthop); static void init_router_targets(); static void cleanup(void); static int setup_listener(int af); static void check_config(void); static void check_testconfig(void); static void check_addr_unique(struct phyint_instance *, struct sockaddr_storage *); static void init_host_targets(void); static void dup_host_targets(struct phyint_instance *desired_pii); static void loopback_cmd(int sock, int family); static boolean_t daemonize(void); static int closefunc(void *, int); static unsigned int process_cmd(int newfd, union mi_commands *mpi); static unsigned int process_query(int fd, mi_query_t *miq); static unsigned int send_addrinfo(int fd, ipmp_addrinfo_t *adinfop); static unsigned int send_groupinfo(int fd, ipmp_groupinfo_t *grinfop); static unsigned int send_grouplist(int fd, ipmp_grouplist_t *grlistp); static unsigned int send_ifinfo(int fd, ipmp_ifinfo_t *ifinfop); static unsigned int send_result(int fd, unsigned int error, int syserror); addrlist_t *localaddrs; /* * Return the current time in milliseconds (from an arbitrary reference) * truncated to fit into an int. Truncation is ok since we are interested * only in differences and not the absolute values. */ uint_t getcurrenttime(void) { uint_t cur_time; /* In ms */ /* * Use of a non-user-adjustable source of time is * required. However millisecond precision is sufficient. * divide by 10^6 */ cur_time = (uint_t)(gethrtime() / 1000000LL); return (cur_time); } uint64_t getcurrentsec(void) { return (gethrtime() / NANOSEC); } /* * Add fd to the set being polled. Returns 0 if ok; -1 if failed. */ int poll_add(int fd) { int i; int new_num; struct pollfd *newfds; retry: /* Check if already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == fd) return (0); } /* Check for empty spot already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == -1) { pollfds[i].fd = fd; return (0); } } /* Allocate space for 32 more fds and initialize to -1 */ new_num = pollfd_num + 32; newfds = realloc(pollfds, new_num * sizeof (struct pollfd)); if (newfds == NULL) { logperror("poll_add: realloc"); return (-1); } for (i = pollfd_num; i < new_num; i++) { newfds[i].fd = -1; newfds[i].events = POLLIN; } pollfd_num = new_num; pollfds = newfds; goto retry; } /* * Remove fd from the set being polled. Returns 0 if ok; -1 if failed. */ int poll_remove(int fd) { int i; /* Check if already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == fd) { pollfds[i].fd = -1; return (0); } } return (-1); } /* * Extract information about the phyint instance. If the phyint instance still * exists in the kernel then set pii_in_use, else clear it. check_if_removed() * will use it to detect phyint instances that don't exist any longer and * remove them, from our database of phyint instances. * Return value: * returns true if the phyint instance exists in the kernel, * returns false otherwise */ static boolean_t pii_process(int af, char *name, struct phyint_instance **pii_p) { int err; struct phyint_instance *pii; struct phyint_instance *pii_other; if (debug & D_PHYINT) logdebug("pii_process(%s %s)\n", AF_STR(af), name); pii = phyint_inst_lookup(af, name); if (pii == NULL) { /* * Phyint instance does not exist in our tables, * create new phyint instance */ pii = phyint_inst_init_from_k(af, name); } else { /* Phyint exists in our tables */ err = phyint_inst_update_from_k(pii); switch (err) { case PI_IOCTL_ERROR: /* Some ioctl error. don't change anything */ pii->pii_in_use = 1; break; case PI_GROUP_CHANGED: case PI_IFINDEX_CHANGED: /* * Interface index or group membership has changed. * Delete the old state and recreate based on the new * state (it may no longer be in a group). */ pii_other = phyint_inst_other(pii); if (pii_other != NULL) phyint_inst_delete(pii_other); phyint_inst_delete(pii); pii = phyint_inst_init_from_k(af, name); break; case PI_DELETED: /* Phyint instance has disappeared from kernel */ pii->pii_in_use = 0; break; case PI_OK: /* Phyint instance exists and is fine */ pii->pii_in_use = 1; break; default: /* Unknown status */ logerr("pii_process: Unknown status %d\n", err); break; } } *pii_p = pii; if (pii != NULL) return (pii->pii_in_use ? _B_TRUE : _B_FALSE); else return (_B_FALSE); } /* * Scan all interfaces to detect changes as well as new and deleted interfaces */ static void initifs() { int i, nlifr; int af; char *cp; char *buf; int sockfd; uint64_t flags; struct lifnum lifn; struct lifconf lifc; struct lifreq lifreq; struct lifreq *lifr; struct logint *li; struct phyint_instance *pii; struct phyint_instance *next_pii; struct phyint_group *pg, *next_pg; char pi_name[LIFNAMSIZ + 1]; if (debug & D_PHYINT) logdebug("initifs: Scanning interfaces\n"); last_initifs_time = getcurrenttime(); /* * Free the existing local address list; we'll build a new list below. */ addrlist_free(&localaddrs); /* * Mark the interfaces so that we can find phyints and logints * which have disappeared from the kernel. pii_process() and * logint_init_from_k() will set {pii,li}_in_use when they find * the interface in the kernel. Also, clear dupaddr bit on probe * logint. check_addr_unique() will set the dupaddr bit on the * probe logint, if the testaddress is not unique. */ for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { pii->pii_in_use = 0; for (li = pii->pii_logint; li != NULL; li = li->li_next) { li->li_in_use = 0; if (pii->pii_probe_logint == li) li->li_dupaddr = 0; } } /* * As above, mark groups so that we can detect IPMP interfaces which * have been removed from the kernel. Also, delete the group address * list since we'll iteratively recreate it below. */ for (pg = phyint_groups; pg != NULL; pg = pg->pg_next) { pg->pg_in_use = _B_FALSE; addrlist_free(&pg->pg_addrs); } lifn.lifn_family = AF_UNSPEC; lifn.lifn_flags = LIFC_ALLZONES | LIFC_UNDER_IPMP; again: if (ioctl(ifsock_v4, SIOCGLIFNUM, (char *)&lifn) < 0) { logperror("initifs: ioctl (get interface count)"); return; } /* * Pad the interface count to detect when additional interfaces have * been configured between SIOCGLIFNUM and SIOCGLIFCONF. */ lifn.lifn_count += 4; if ((buf = calloc(lifn.lifn_count, sizeof (struct lifreq))) == NULL) { logperror("initifs: calloc"); return; } lifc.lifc_family = AF_UNSPEC; lifc.lifc_flags = LIFC_ALLZONES | LIFC_UNDER_IPMP; lifc.lifc_len = lifn.lifn_count * sizeof (struct lifreq); lifc.lifc_buf = buf; if (ioctl(ifsock_v4, SIOCGLIFCONF, (char *)&lifc) < 0) { logperror("initifs: ioctl (get interface configuration)"); free(buf); return; } /* * If every lifr_req slot is taken, then additional interfaces must * have been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF. * Recalculate to make sure we didn't miss any interfaces. */ nlifr = lifc.lifc_len / sizeof (struct lifreq); if (nlifr >= lifn.lifn_count) { free(buf); goto again; } /* * Walk through the lifreqs returned by SIOGGLIFCONF, and refresh the * global list of addresses, phyint groups, phyints, and logints. */ for (lifr = lifc.lifc_req, i = 0; i < nlifr; i++, lifr++) { af = lifr->lifr_addr.ss_family; sockfd = (af == AF_INET) ? ifsock_v4 : ifsock_v6; (void) strlcpy(lifreq.lifr_name, lifr->lifr_name, LIFNAMSIZ); if (ioctl(sockfd, SIOCGLIFFLAGS, &lifreq) == -1) { if (errno != ENXIO) logperror("initifs: ioctl (SIOCGLIFFLAGS)"); continue; } flags = lifreq.lifr_flags; /* * If the address is IFF_UP, add it to the local address list. * (We ignore addresses that aren't IFF_UP since another node * might legitimately have that address IFF_UP.) */ if (flags & IFF_UP) { (void) addrlist_add(&localaddrs, lifr->lifr_name, flags, &lifr->lifr_addr); } /* * If this address is on an IPMP meta-interface, update our * phyint_group information (either by recording that group * still exists or creating a new group), and track what * group the address is part of. */ if (flags & IFF_IPMP) { if (ioctl(sockfd, SIOCGLIFGROUPNAME, &lifreq) == -1) { if (errno != ENXIO) logperror("initifs: ioctl " "(SIOCGLIFGROUPNAME)"); continue; } pg = phyint_group_lookup(lifreq.lifr_groupname); if (pg == NULL) { pg = phyint_group_create(lifreq.lifr_groupname); if (pg == NULL) { logerr("initifs: cannot create group " "%s\n", lifreq.lifr_groupname); continue; } phyint_group_insert(pg); } pg->pg_in_use = _B_TRUE; /* * Add this to the group's list of data addresses. */ if (!addrlist_add(&pg->pg_addrs, lifr->lifr_name, flags, &lifr->lifr_addr)) { logerr("initifs: insufficient memory to track " "data address information for %s\n", lifr->lifr_name); } continue; } /* * This isn't an address on an IPMP meta-interface, so it's * either on an underlying interface or not related to any * group. Update our phyint and logint information (via * pii_process() and logint_init_from_k()) -- but first, * convert the logint name to a phyint name so we can call * pii_process(). */ (void) strlcpy(pi_name, lifr->lifr_name, sizeof (pi_name)); if ((cp = strchr(pi_name, IF_SEPARATOR)) != NULL) *cp = '\0'; if (pii_process(af, pi_name, &pii)) { /* The phyint is fine. So process the logint */ logint_init_from_k(pii, lifr->lifr_name); check_addr_unique(pii, &lifr->lifr_addr); } } free(buf); /* * Scan for groups, phyints and logints that have disappeared from the * kernel, and delete them. */ for (pii = phyint_instances; pii != NULL; pii = next_pii) { next_pii = pii->pii_next; check_if_removed(pii); } for (pg = phyint_groups; pg != NULL; pg = next_pg) { next_pg = pg->pg_next; if (!pg->pg_in_use) { phyint_group_delete(pg); continue; } /* * Refresh the group's state. This is necessary since the * group's state is defined by the set of usable interfaces in * the group, and an interface is considered unusable if all * of its addresses are down. When an address goes down/up, * the RTM_DELADDR/RTM_NEWADDR brings us through here. */ phyint_group_refresh_state(pg); } /* * Select a test address for sending probes on each phyint instance */ select_test_ifs(); /* * Handle link up/down notifications. */ process_link_state_changes(); } /* * Check that a given test address is unique across all of the interfaces in a * group. (e.g., IPv6 link-locals may not be inherently unique, and binding * to such an (IFF_NOFAILOVER) address can produce unexpected results.) * Any issues will be reported by check_testconfig(). */ static void check_addr_unique(struct phyint_instance *ourpii, struct sockaddr_storage *ss) { struct phyint *pi; struct phyint_group *pg; struct in6_addr addr; struct phyint_instance *pii; struct sockaddr_in *sin; if (ss->ss_family == AF_INET) { sin = (struct sockaddr_in *)ss; IN6_INADDR_TO_V4MAPPED(&sin->sin_addr, &addr); } else { assert(ss->ss_family == AF_INET6); addr = ((struct sockaddr_in6 *)ss)->sin6_addr; } /* * For anonymous groups, every interface is assumed to be on its own * link, so there is no chance of overlapping addresses. */ pg = ourpii->pii_phyint->pi_group; if (pg == phyint_anongroup) return; /* * Walk the list of phyint instances in the group and check for test * addresses matching ours. Of course, we skip ourself. */ for (pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext) { pii = PHYINT_INSTANCE(pi, ss->ss_family); if (pii == NULL || pii == ourpii || pii->pii_probe_logint == NULL) continue; /* * If this test address is not unique, set the dupaddr bit. */ if (IN6_ARE_ADDR_EQUAL(&addr, &pii->pii_probe_logint->li_addr)) pii->pii_probe_logint->li_dupaddr = 1; } } /* * Stop probing an interface. Called when an interface is offlined. * The probe socket is closed on each interface instance, and the * interface state set to PI_OFFLINE. */ void stop_probing(struct phyint *pi) { struct phyint_instance *pii; pii = pi->pi_v4; if (pii != NULL) { if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_TRUE); pii->pii_probe_logint = NULL; } pii = pi->pi_v6; if (pii != NULL) { if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_TRUE); pii->pii_probe_logint = NULL; } phyint_chstate(pi, PI_OFFLINE); } enum { BAD_TESTFLAGS, OK_TESTFLAGS, BEST_TESTFLAGS }; /* * Rate the provided test flags. By definition, IFF_NOFAILOVER must be set. * IFF_UP must also be set so that the associated address can be used as a * source address. Further, we must be able to exchange packets with local * destinations, so IFF_NOXMIT and IFF_NOLOCAL must be clear. For historical * reasons, we have a proclivity for IFF_DEPRECATED IPv4 test addresses. */ static int rate_testflags(uint64_t flags) { if ((flags & (IFF_NOFAILOVER | IFF_UP)) != (IFF_NOFAILOVER | IFF_UP)) return (BAD_TESTFLAGS); if ((flags & (IFF_NOXMIT | IFF_NOLOCAL)) != 0) return (BAD_TESTFLAGS); if ((flags & (IFF_IPV6 | IFF_DEPRECATED)) == IFF_DEPRECATED) return (BEST_TESTFLAGS); if ((flags & (IFF_IPV6 | IFF_DEPRECATED)) == IFF_IPV6) return (BEST_TESTFLAGS); return (OK_TESTFLAGS); } /* * Attempt to select a test address for each phyint instance. * Call phyint_inst_sockinit() to complete the initializations. */ static void select_test_ifs(void) { struct phyint *pi; struct phyint_instance *pii; struct phyint_instance *next_pii; struct logint *li; struct logint *probe_logint; boolean_t target_scan_reqd = _B_FALSE; int rating; if (debug & D_PHYINT) logdebug("select_test_ifs\n"); /* * For each phyint instance, do the test address selection */ for (pii = phyint_instances; pii != NULL; pii = next_pii) { next_pii = pii->pii_next; probe_logint = NULL; /* * An interface that is offline should not be probed. * IFF_OFFLINE interfaces should always be PI_OFFLINE * unless some other entity has set the offline flag. */ if (pii->pii_phyint->pi_flags & IFF_OFFLINE) { if (pii->pii_phyint->pi_state != PI_OFFLINE) { logerr("shouldn't be probing offline" " interface %s (state is: %u)." " Stopping probes.\n", pii->pii_phyint->pi_name, pii->pii_phyint->pi_state); stop_probing(pii->pii_phyint); } continue; } else { /* * If something cleared IFF_OFFLINE (e.g., by accident * because the SIOCGLIFFLAGS/SIOCSLIFFLAGS sequence is * inherently racy), the phyint may still be offline. * Just ignore it. */ if (pii->pii_phyint->pi_state == PI_OFFLINE) continue; } li = pii->pii_probe_logint; if (li != NULL) { /* * We've already got a test address; only proceed * if it's suboptimal. */ if (rate_testflags(li->li_flags) == BEST_TESTFLAGS) continue; } /* * Walk the logints of this phyint instance, and select * the best available test address */ for (li = pii->pii_logint; li != NULL; li = li->li_next) { /* * Skip 0.0.0.0 addresses, as those are never * actually usable. */ if (pii->pii_af == AF_INET && IN6_IS_ADDR_V4MAPPED_ANY(&li->li_addr)) continue; /* * Skip any IPv6 logints that are not link-local, * since we should always have a link-local address * anyway and in6_data() expects link-local replies. */ if (pii->pii_af == AF_INET6 && !IN6_IS_ADDR_LINKLOCAL(&li->li_addr)) continue; /* * Rate the testflags. If we've found an optimal * match, then break out; otherwise, record the most * recent OK one. */ rating = rate_testflags(li->li_flags); if (rating == BAD_TESTFLAGS) continue; probe_logint = li; if (rating == BEST_TESTFLAGS) break; } /* * If the probe logint has changed, ditch the old one. */ if (pii->pii_probe_logint != NULL && pii->pii_probe_logint != probe_logint) { if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_TRUE); pii->pii_probe_logint = NULL; } if (probe_logint == NULL) { /* * We don't have a test address; zero out the probe * stats array since it is no longer relevant. * Optimize by checking if it is already zeroed out. */ int pr_ndx; pr_ndx = PROBE_INDEX_PREV(pii->pii_probe_next); if (pii->pii_probes[pr_ndx].pr_status != PR_UNUSED) { clear_pii_probe_stats(pii); reset_crtt_all(pii->pii_phyint); } continue; } else if (probe_logint == pii->pii_probe_logint) { /* * If we didn't find any new test addr, go to the * next phyint. */ continue; } /* * The phyint is either being assigned a new testaddr * or is being assigned a testaddr for the 1st time. * Need to initialize the phyint socket */ pii->pii_probe_logint = probe_logint; if (!phyint_inst_sockinit(pii)) { if (debug & D_PHYINT) { logdebug("select_test_ifs: " "phyint_sockinit failed\n"); } phyint_inst_delete(pii); continue; } /* * This phyint instance is now enabled for probes; this * impacts our state machine in two ways: * * 1. If we're probe *capable* as well (i.e., we have * probe targets) and the interface is in PI_NOTARGETS, * then transition to PI_RUNNING. * * 2. If we're not probe capable, and the other phyint * instance is also not probe capable, and we were in * PI_RUNNING, then transition to PI_NOTARGETS. * * Also see the state diagram in mpd_probe.c. */ if (PROBE_CAPABLE(pii)) { if (pii->pii_phyint->pi_state == PI_NOTARGETS) phyint_chstate(pii->pii_phyint, PI_RUNNING); } else if (!PROBE_CAPABLE(phyint_inst_other(pii))) { if (pii->pii_phyint->pi_state == PI_RUNNING) phyint_chstate(pii->pii_phyint, PI_NOTARGETS); } /* * If no targets are currently known for this phyint * we need to call init_router_targets. Since * init_router_targets() initializes the list of targets * for all phyints it is done below the loop. */ if (pii->pii_targets == NULL) target_scan_reqd = _B_TRUE; /* * Start the probe timer for this instance. */ if (!pii->pii_basetime_inited && PROBE_ENABLED(pii)) { start_timer(pii); pii->pii_basetime_inited = 1; } } /* * Scan the interface list for any interfaces that are PI_FAILED or * PI_NOTARGETS but no longer enabled to send probes, and call * phyint_check_for_repair() to see if the link state indicates that * the interface should be repaired. Also see the state diagram in * mpd_probe.c. */ for (pi = phyints; pi != NULL; pi = pi->pi_next) { if ((!PROBE_ENABLED(pi->pi_v4) && !PROBE_ENABLED(pi->pi_v6)) && (pi->pi_state == PI_FAILED || pi->pi_state == PI_NOTARGETS)) { phyint_check_for_repair(pi); } } check_testconfig(); /* * Try to populate the target list. init_router_targets populates * the target list from the routing table. If our target list is * still empty, init_host_targets adds host targets based on the * host target list of other phyints in the group. */ if (target_scan_reqd) { init_router_targets(); init_host_targets(); } } /* * Check test address configuration, and log notices/errors if appropriate. * Note that this function only logs pre-existing conditions (e.g., that * probe-based failure detection is disabled). */ static void check_testconfig(void) { struct phyint *pi; struct logint *li; char abuf[INET6_ADDRSTRLEN]; int pri; for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pi->pi_flags & IFF_OFFLINE) continue; if (PROBE_ENABLED(pi->pi_v4) || PROBE_ENABLED(pi->pi_v6)) { if (pi->pi_taddrmsg_printed || pi->pi_duptaddrmsg_printed) { if (pi->pi_duptaddrmsg_printed) pri = LOG_ERR; else pri = LOG_INFO; logmsg(pri, "Test address now configured on " "interface %s; enabling probe-based " "failure detection on it\n", pi->pi_name); pi->pi_taddrmsg_printed = 0; pi->pi_duptaddrmsg_printed = 0; } continue; } li = NULL; if (pi->pi_v4 != NULL && pi->pi_v4->pii_probe_logint != NULL && pi->pi_v4->pii_probe_logint->li_dupaddr) li = pi->pi_v4->pii_probe_logint; if (pi->pi_v6 != NULL && pi->pi_v6->pii_probe_logint != NULL && pi->pi_v6->pii_probe_logint->li_dupaddr) li = pi->pi_v6->pii_probe_logint; if (li != NULL && li->li_dupaddr) { if (pi->pi_duptaddrmsg_printed) continue; logerr("Test address %s is not unique in group; " "disabling probe-based failure detection on %s\n", pr_addr(li->li_phyint_inst->pii_af, li->li_addr, abuf, sizeof (abuf)), pi->pi_name); pi->pi_duptaddrmsg_printed = 1; continue; } if (getcurrentsec() < pi->pi_taddrthresh) continue; if (!pi->pi_taddrmsg_printed) { logtrace("No test address configured on interface %s; " "disabling probe-based failure detection on it\n", pi->pi_name); pi->pi_taddrmsg_printed = 1; } } } /* * Check phyint group configuration, to detect any inconsistencies, * and log an error message. This is called from runtimeouts every * 20 secs. But the error message is displayed once. If the * consistency is resolved by the admin, a recovery message is displayed * once. */ static void check_config(void) { struct phyint_group *pg; struct phyint *pi; boolean_t v4_in_group; boolean_t v6_in_group; /* * All phyints of a group must be homogeneous to ensure that they can * take over for one another. If any phyint in a group has IPv4 * plumbed, check that all phyints have IPv4 plumbed. Do a similar * check for IPv6. */ for (pg = phyint_groups; pg != NULL; pg = pg->pg_next) { if (pg == phyint_anongroup) continue; v4_in_group = _B_FALSE; v6_in_group = _B_FALSE; /* * 1st pass. Determine if at least 1 phyint in the group * has IPv4 plumbed and if so set v4_in_group to true. * Repeat similarly for IPv6. */ for (pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext) { if (pi->pi_v4 != NULL) v4_in_group = _B_TRUE; if (pi->pi_v6 != NULL) v6_in_group = _B_TRUE; } /* * 2nd pass. If v4_in_group is true, check that phyint * has IPv4 plumbed. Repeat similarly for IPv6. Print * out a message the 1st time only. */ for (pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext) { if (pi->pi_flags & IFF_OFFLINE) continue; if (v4_in_group == _B_TRUE && pi->pi_v4 == NULL) { if (!pi->pi_cfgmsg_printed) { logerr("IP interface %s in group %s is" " not plumbed for IPv4, affecting" " IPv4 connectivity\n", pi->pi_name, pi->pi_group->pg_name); pi->pi_cfgmsg_printed = 1; } } else if (v6_in_group == _B_TRUE && pi->pi_v6 == NULL) { if (!pi->pi_cfgmsg_printed) { logerr("IP interface %s in group %s is" " not plumbed for IPv6, affecting" " IPv6 connectivity\n", pi->pi_name, pi->pi_group->pg_name); pi->pi_cfgmsg_printed = 1; } } else { /* * The phyint matches the group configuration, * if we have reached this point. If it was * improperly configured earlier, log an * error recovery message */ if (pi->pi_cfgmsg_printed) { logerr("IP interface %s is now" " consistent with group %s " " and connectivity is restored\n", pi->pi_name, pi->pi_group->pg_name); pi->pi_cfgmsg_printed = 0; } } } } } /* * Timer mechanism using relative time (in milliseconds) from the * previous timer event. Timers exceeding TIMER_INFINITY milliseconds * will fire after TIMER_INFINITY milliseconds. * Unsigned arithmetic note: We assume a 32-bit circular sequence space for * time values. Hence 2 consecutive timer events cannot be spaced farther * than 0x7fffffff. We call this TIMER_INFINITY, and it is the maximum value * that can be passed for the delay parameter of timer_schedule() */ static uint_t timer_next; /* Currently scheduled timeout */ static boolean_t timer_active = _B_FALSE; /* SIGALRM has not yet occurred */ static void timer_init(void) { timer_next = getcurrenttime() + TIMER_INFINITY; /* * The call to run_timeouts() will get the timer started * Since there are no phyints at this point, the timer will * be set for IF_SCAN_INTERVAL ms. */ run_timeouts(); } /* * Make sure the next SIGALRM occurs delay milliseconds from the current * time if not earlier. We are interested only in time differences. */ void timer_schedule(uint_t delay) { uint_t now; struct itimerval itimerval; if (debug & D_TIMER) logdebug("timer_schedule(%u)\n", delay); assert(delay <= TIMER_INFINITY); now = getcurrenttime(); if (delay == 0) { /* Minimum allowed delay */ delay = 1; } /* Will this timer occur before the currently scheduled SIGALRM? */ if (timer_active && TIME_GE(now + delay, timer_next)) { if (debug & D_TIMER) { logdebug("timer_schedule(%u) - no action: " "now %u next %u\n", delay, now, timer_next); } return; } timer_next = now + delay; itimerval.it_value.tv_sec = delay / 1000; itimerval.it_value.tv_usec = (delay % 1000) * 1000; itimerval.it_interval.tv_sec = 0; itimerval.it_interval.tv_usec = 0; if (debug & D_TIMER) { logdebug("timer_schedule(%u): sec %ld usec %ld\n", delay, itimerval.it_value.tv_sec, itimerval.it_value.tv_usec); } timer_active = _B_TRUE; if (setitimer(ITIMER_REAL, &itimerval, NULL) < 0) { logperror("timer_schedule: setitimer"); exit(2); } } static void timer_cancel(void) { struct itimerval itimerval; if (debug & D_TIMER) logdebug("timer_cancel()\n"); bzero(&itimerval, sizeof (itimerval)); if (setitimer(ITIMER_REAL, &itimerval, NULL) < 0) logperror("timer_cancel: setitimer"); } /* * Timer has fired. Determine when the next timer event will occur by asking * all the timer routines. Should not be called from a timer routine. */ static void run_timeouts(void) { uint_t next; uint_t next_event_time; struct phyint_instance *pii; struct phyint_instance *next_pii; static boolean_t timeout_running; /* assert that recursive timeouts don't happen. */ assert(!timeout_running); timeout_running = _B_TRUE; if (debug & D_TIMER) logdebug("run_timeouts()\n"); if ((getcurrenttime() - last_initifs_time) > IF_SCAN_INTERVAL) { initifs(); check_config(); } next = TIMER_INFINITY; for (pii = phyint_instances; pii != NULL; pii = next_pii) { next_pii = pii->pii_next; next_event_time = phyint_inst_timer(pii); if (next_event_time != TIMER_INFINITY && next_event_time < next) next = next_event_time; if (debug & D_TIMER) { logdebug("run_timeouts(%s %s): next scheduled for" " this phyint inst %u, next scheduled global" " %u ms\n", AF_STR(pii->pii_af), pii->pii_phyint->pi_name, next_event_time, next); } } /* * Make sure initifs() is called at least once every * IF_SCAN_INTERVAL, to make sure that we are in sync * with the kernel, in case we have missed any routing * socket messages. */ if (next > IF_SCAN_INTERVAL) next = IF_SCAN_INTERVAL; if (debug & D_TIMER) logdebug("run_timeouts: %u ms\n", next); timer_schedule(next); timeout_running = _B_FALSE; } static int eventpipe_read = -1; /* Used for synchronous signal delivery */ static int eventpipe_write = -1; boolean_t cleanup_started = _B_FALSE; /* true if we're going away */ /* * Ensure that signals are processed synchronously with the rest of * the code by just writing a one character signal number on the pipe. * The poll loop will pick this up and process the signal event. */ static void sig_handler(int signo) { uchar_t buf = (uchar_t)signo; /* * Don't write to pipe if cleanup has already begun. cleanup() * might have closed the pipe already */ if (cleanup_started) return; if (eventpipe_write == -1) { logerr("sig_handler: no pipe found\n"); return; } if (write(eventpipe_write, &buf, sizeof (buf)) < 0) logperror("sig_handler: write"); } extern struct probes_missed probes_missed; /* * Pick up a signal "byte" from the pipe and process it. */ static void in_signal(int fd) { uchar_t buf; uint64_t sent, acked, lost, unacked, unknown; struct phyint_instance *pii; int pr_ndx; switch (read(fd, &buf, sizeof (buf))) { case -1: logperror("in_signal: read"); exit(1); /* NOTREACHED */ case 1: break; case 0: logerr("in_signal: read end of file\n"); exit(1); /* NOTREACHED */ default: logerr("in_signal: read > 1\n"); exit(1); } if (debug & D_TIMER) logdebug("in_signal() got %d\n", buf); switch (buf) { case SIGALRM: if (debug & D_TIMER) { uint_t now = getcurrenttime(); logdebug("in_signal(SIGALRM) delta %u\n", now - timer_next); } timer_active = _B_FALSE; run_timeouts(); break; case SIGUSR1: logdebug("Printing configuration:\n"); /* Print out the internal tables */ phyint_inst_print_all(); /* * Print out the accumulated statistics about missed * probes (happens due to scheduling delay). */ logerr("Missed sending total of %d probes spread over" " %d occurrences\n", probes_missed.pm_nprobes, probes_missed.pm_ntimes); /* * Print out the accumulated statistics about probes * that were sent. */ for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { unacked = 0; acked = pii->pii_cum_stats.acked; lost = pii->pii_cum_stats.lost; sent = pii->pii_cum_stats.sent; unknown = pii->pii_cum_stats.unknown; for (pr_ndx = 0; pr_ndx < PROBE_STATS_COUNT; pr_ndx++) { switch (pii->pii_probes[pr_ndx].pr_status) { case PR_ACKED: acked++; break; case PR_LOST: lost++; break; case PR_UNACKED: unacked++; break; } } logerr("\nProbe stats on (%s %s)\n" "Number of probes sent %lld\n" "Number of probe acks received %lld\n" "Number of probes/acks lost %lld\n" "Number of valid unacknowledged probes %lld\n" "Number of ambiguous probe acks received %lld\n", AF_STR(pii->pii_af), pii->pii_name, sent, acked, lost, unacked, unknown); } break; case SIGHUP: logerr("SIGHUP: restart and reread config file\n"); /* * Cancel the interval timer. Needed since setitimer() uses * alarm() and the time left is inherited across exec(), and * thus the SIGALRM may be delivered before a handler has been * setup, causing in.mpathd to erroneously exit. */ timer_cancel(); cleanup(); (void) execv(argv0[0], argv0); _exit(0177); /* NOTREACHED */ case SIGINT: case SIGTERM: case SIGQUIT: cleanup(); exit(0); /* NOTREACHED */ default: logerr("in_signal: unknown signal: %d\n", buf); } } static void cleanup(void) { struct phyint_instance *pii; struct phyint_instance *next_pii; /* * Make sure that we don't write to eventpipe in * sig_handler() if any signal notably SIGALRM, * occurs after we close the eventpipe descriptor below */ cleanup_started = _B_TRUE; for (pii = phyint_instances; pii != NULL; pii = next_pii) { next_pii = pii->pii_next; phyint_inst_delete(pii); } (void) close(ifsock_v4); (void) close(ifsock_v6); (void) close(rtsock_v4); (void) close(rtsock_v6); (void) close(lsock_v4); (void) close(lsock_v6); (void) close(0); (void) close(1); (void) close(2); (void) close(mibfd); (void) close(eventpipe_read); (void) close(eventpipe_write); } /* * Create pipe for signal delivery and set up signal handlers. */ static void setup_eventpipe(void) { int fds[2]; struct sigaction act; if ((pipe(fds)) < 0) { logperror("setup_eventpipe: pipe"); exit(1); } eventpipe_read = fds[0]; eventpipe_write = fds[1]; if (poll_add(eventpipe_read) == -1) { exit(1); } act.sa_handler = sig_handler; act.sa_flags = SA_RESTART; (void) sigaction(SIGALRM, &act, NULL); (void) sigset(SIGHUP, sig_handler); (void) sigset(SIGUSR1, sig_handler); (void) sigset(SIGTERM, sig_handler); (void) sigset(SIGINT, sig_handler); (void) sigset(SIGQUIT, sig_handler); } /* * Create a routing socket for receiving RTM_IFINFO messages. */ static int setup_rtsock(int af) { int s; int flags; int aware = RTAW_UNDER_IPMP; s = socket(PF_ROUTE, SOCK_RAW, af); if (s == -1) { logperror("setup_rtsock: socket PF_ROUTE"); exit(1); } if (setsockopt(s, SOL_ROUTE, RT_AWARE, &aware, sizeof (aware)) == -1) { logperror("setup_rtsock: setsockopt RT_AWARE"); (void) close(s); exit(1); } if ((flags = fcntl(s, F_GETFL, 0)) < 0) { logperror("setup_rtsock: fcntl F_GETFL"); (void) close(s); exit(1); } if ((fcntl(s, F_SETFL, flags | O_NONBLOCK)) < 0) { logperror("setup_rtsock: fcntl F_SETFL"); (void) close(s); exit(1); } if (poll_add(s) == -1) { (void) close(s); exit(1); } return (s); } /* * Process an RTM_IFINFO message received on a routing socket. * The return value indicates whether a full interface scan is required. * Link up/down notifications are reflected in the IFF_RUNNING flag. * If just the state of the IFF_RUNNING interface flag has changed, a * a full interface scan isn't required. */ static boolean_t process_rtm_ifinfo(if_msghdr_t *ifm, int type) { struct sockaddr_dl *sdl; struct phyint *pi; uint64_t old_flags; struct phyint_instance *pii; assert(ifm->ifm_type == RTM_IFINFO && ifm->ifm_addrs == RTA_IFP); /* * Although the sockaddr_dl structure is directly after the * if_msghdr_t structure. At the time of writing, the size of the * if_msghdr_t structure is different on 32 and 64 bit kernels, due * to the presence of a timeval structure, which contains longs, * in the if_data structure. Anyway, we know where the message ends, * so we work backwards to get the start of the sockaddr_dl structure. */ /*LINTED*/ sdl = (struct sockaddr_dl *)((char *)ifm + ifm->ifm_msglen - sizeof (struct sockaddr_dl)); assert(sdl->sdl_family == AF_LINK); /* * The interface name is in sdl_data. * RTM_IFINFO messages are only generated for logical interface * zero, so there is no colon and logical interface number to * strip from the name. The name is not null terminated, but * there should be enough space in sdl_data to add the null. */ if (sdl->sdl_nlen >= sizeof (sdl->sdl_data)) { if (debug & D_LINKNOTE) logdebug("process_rtm_ifinfo: phyint name too long\n"); return (_B_TRUE); } sdl->sdl_data[sdl->sdl_nlen] = 0; pi = phyint_lookup(sdl->sdl_data); if (pi == NULL) { if (debug & D_LINKNOTE) logdebug("process_rtm_ifinfo: phyint lookup failed" " for %s\n", sdl->sdl_data); return (_B_TRUE); } /* * We want to try and avoid doing a full interface scan for * link state notifications from the datalink layer, as indicated * by the state of the IFF_RUNNING flag. If just the * IFF_RUNNING flag has changed state, the link state changes * are processed without a full scan. * If there is both an IPv4 and IPv6 instance associated with * the physical interface, we will get an RTM_IFINFO message * for each instance. If we just maintained a single copy of * the physical interface flags, it would appear that no flags * had changed when the second message is processed, leading us * to believe that the message wasn't generated by a flags change, * and that a full interface scan is required. * To get around this problem, two additional copies of the flags * are kept, one copy for each instance. These are only used in * this routine. At any one time, all three copies of the flags * should be identical except for the IFF_RUNNING flag. The * copy of the flags in the "phyint" structure is always up to * date. */ pii = (type == AF_INET) ? pi->pi_v4 : pi->pi_v6; if (pii == NULL) { if (debug & D_LINKNOTE) logdebug("process_rtm_ifinfo: no instance of address " "family %s for %s\n", AF_STR(type), pi->pi_name); return (_B_TRUE); } old_flags = pii->pii_flags; pii->pii_flags = PHYINT_FLAGS(ifm->ifm_flags); pi->pi_flags = pii->pii_flags; if (debug & D_LINKNOTE) { logdebug("process_rtm_ifinfo: %s address family: %s, " "old flags: %llx, new flags: %llx\n", pi->pi_name, AF_STR(type), old_flags, pi->pi_flags); } /* * If IFF_STANDBY has changed, indicate that the interface has changed * types and refresh IFF_INACTIVE if need be. */ if ((old_flags ^ pii->pii_flags) & IFF_STANDBY) { phyint_changed(pi); if (pii->pii_flags & IFF_STANDBY) phyint_standby_refresh_inactive(pi); } /* Has just the IFF_RUNNING flag changed state ? */ if ((old_flags ^ pii->pii_flags) != IFF_RUNNING) { struct phyint_instance *pii_other; /* * It wasn't just a link state change. Update * the other instance's copy of the flags. */ pii_other = phyint_inst_other(pii); if (pii_other != NULL) pii_other->pii_flags = pii->pii_flags; return (_B_TRUE); } return (_B_FALSE); } /* * Retrieve as many routing socket messages as possible, and try to * empty the routing sockets. Initiate full scan of targets or interfaces * as needed. * We listen on separate IPv4 an IPv6 sockets so that we can accurately * detect changes in certain flags (see "process_rtm_ifinfo()" above). */ static void process_rtsock(int rtsock_v4, int rtsock_v6) { int nbytes; int64_t msg[2048 / 8]; struct rt_msghdr *rtm; boolean_t need_if_scan = _B_FALSE; boolean_t need_rt_scan = _B_FALSE; boolean_t rtm_ifinfo_seen = _B_FALSE; int type; /* Read as many messages as possible and try to empty the sockets */ for (type = AF_INET; ; type = AF_INET6) { for (;;) { nbytes = read((type == AF_INET) ? rtsock_v4 : rtsock_v6, msg, sizeof (msg)); if (nbytes <= 0) { /* No more messages */ break; } rtm = (struct rt_msghdr *)msg; if (rtm->rtm_version != RTM_VERSION) { logerr("process_rtsock: version %d " "not understood\n", rtm->rtm_version); break; } if (debug & D_PHYINT) { logdebug("process_rtsock: message %d\n", rtm->rtm_type); } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: /* * Some logical interface has changed, * have to scan everything to determine * what actually changed. */ need_if_scan = _B_TRUE; break; case RTM_IFINFO: rtm_ifinfo_seen = _B_TRUE; need_if_scan |= process_rtm_ifinfo( (if_msghdr_t *)rtm, type); break; case RTM_ADD: case RTM_DELETE: case RTM_CHANGE: case RTM_OLDADD: case RTM_OLDDEL: need_rt_scan = _B_TRUE; break; default: /* Not interesting */ break; } } if (type == AF_INET6) break; } if (need_if_scan) { if (debug & D_LINKNOTE && rtm_ifinfo_seen) logdebug("process_rtsock: synchronizing with kernel\n"); initifs(); } else if (rtm_ifinfo_seen) { if (debug & D_LINKNOTE) logdebug("process_rtsock: " "link up/down notification(s) seen\n"); process_link_state_changes(); } if (need_rt_scan) init_router_targets(); } /* * Look if the phyint instance or one of its logints have been removed from * the kernel and take appropriate action. * Uses {pii,li}_in_use. */ static void check_if_removed(struct phyint_instance *pii) { struct logint *li; struct logint *next_li; /* Detect phyints that have been removed from the kernel. */ if (!pii->pii_in_use) { logtrace("%s %s has been removed from kernel\n", AF_STR(pii->pii_af), pii->pii_phyint->pi_name); phyint_inst_delete(pii); } else { /* Detect logints that have been removed. */ for (li = pii->pii_logint; li != NULL; li = next_li) { next_li = li->li_next; if (!li->li_in_use) { logint_delete(li); } } } } /* * Parse the supplied mib2 information to extract the routing information * table. Process the routing table to get the list of known onlink routers * and update our database. These onlink routers will serve as probe * targets. */ static void update_router_list(mib_item_t *item) { for (; item != NULL; item = item->mi_next) { if (item->mi_opthdr.name == 0) continue; if (item->mi_opthdr.level == MIB2_IP && item->mi_opthdr.name == MIB2_IP_ROUTE) { ire_process_v4((mib2_ipRouteEntry_t *)item->mi_valp, item->mi_opthdr.len); } else if (item->mi_opthdr.level == MIB2_IP6 && item->mi_opthdr.name == MIB2_IP6_ROUTE) { ire_process_v6((mib2_ipv6RouteEntry_t *)item->mi_valp, item->mi_opthdr.len); } } } /* * Convert octet `octp' to a phyint name and store in `ifname' */ static void oct2ifname(const Octet_t *octp, char *ifname, size_t ifsize) { char *cp; size_t len = MIN(octp->o_length, ifsize - 1); (void) strncpy(ifname, octp->o_bytes, len); ifname[len] = '\0'; if ((cp = strchr(ifname, IF_SEPARATOR)) != NULL) *cp = '\0'; } /* * Examine the IPv4 routing table `buf' for possible targets. For each * possible target, if it's on the same subnet an interface route, pass * it to router_add_common() for further consideration. */ static void ire_process_v4(mib2_ipRouteEntry_t *buf, size_t len) { char ifname[LIFNAMSIZ]; mib2_ipRouteEntry_t *rp, *rp1, *endp; struct in_addr nexthop_v4; struct in6_addr nexthop; if (debug & D_TARGET) logdebug("ire_process_v4(len %d)\n", len); if (len == 0) return; assert((len % ipRouteEntrySize) == 0); endp = buf + (len / ipRouteEntrySize); /* * Scan the routing table entries for any IRE_OFFSUBNET entries, and * cross-reference them with the interface routes to determine if * they're possible probe targets. */ for (rp = buf; rp < endp; rp++) { if (!(rp->ipRouteInfo.re_ire_type & IRE_OFFSUBNET)) continue; /* Get the nexthop address. */ nexthop_v4.s_addr = rp->ipRouteNextHop; /* * Rescan the routing table looking for interface routes that * are on the same subnet, and try to add them. If they're * not relevant (e.g., the interface route isn't part of an * IPMP group, router_add_common() will discard). */ for (rp1 = buf; rp1 < endp; rp1++) { if (!(rp1->ipRouteInfo.re_ire_type & IRE_INTERFACE) || rp1->ipRouteIfIndex.o_length == 0) continue; if ((rp1->ipRouteDest & rp1->ipRouteMask) != (nexthop_v4.s_addr & rp1->ipRouteMask)) continue; oct2ifname(&rp1->ipRouteIfIndex, ifname, LIFNAMSIZ); IN6_INADDR_TO_V4MAPPED(&nexthop_v4, &nexthop); router_add_common(AF_INET, ifname, nexthop); } } } void router_add_common(int af, char *ifname, struct in6_addr nexthop) { struct phyint_instance *pii; struct phyint *pi; if (debug & D_TARGET) logdebug("router_add_common(%s %s)\n", AF_STR(af), ifname); /* * Retrieve the phyint instance; bail if it's not known to us yet. */ pii = phyint_inst_lookup(af, ifname); if (pii == NULL) return; /* * Don't use our own addresses as targets. */ if (own_address(nexthop)) return; /* * If the phyint is part a named group, then add the address to all * members of the group; note that this is suboptimal in the IPv4 case * as it has already been added to all matching interfaces in * ire_process_v4(). Otherwise, add the address only to the phyint * itself, since other phyints in the anongroup may not be on the same * subnet. */ pi = pii->pii_phyint; if (pi->pi_group == phyint_anongroup) { target_add(pii, nexthop, _B_TRUE); } else { pi = pi->pi_group->pg_phyint; for (; pi != NULL; pi = pi->pi_pgnext) target_add(PHYINT_INSTANCE(pi, af), nexthop, _B_TRUE); } } /* * Examine the IPv6 routing table `buf' for possible link-local targets, and * pass any contenders to router_add_common() for further consideration. */ static void ire_process_v6(mib2_ipv6RouteEntry_t *buf, size_t len) { struct lifreq lifr; char ifname[LIFNAMSIZ]; char grname[LIFGRNAMSIZ]; mib2_ipv6RouteEntry_t *rp, *rp1, *endp; struct in6_addr nexthop_v6; if (debug & D_TARGET) logdebug("ire_process_v6(len %d)\n", len); if (len == 0) return; assert((len % ipv6RouteEntrySize) == 0); endp = buf + (len / ipv6RouteEntrySize); /* * Scan the routing table entries for any IRE_OFFSUBNET entries, and * cross-reference them with the interface routes to determine if * they're possible probe targets. */ for (rp = buf; rp < endp; rp++) { if (!(rp->ipv6RouteInfo.re_ire_type & IRE_OFFSUBNET) || !IN6_IS_ADDR_LINKLOCAL(&rp->ipv6RouteNextHop)) continue; /* Get the nexthop address. */ nexthop_v6 = rp->ipv6RouteNextHop; /* * The interface name should always exist for link-locals; * we use it to map this entry to an IPMP group name. */ if (rp->ipv6RouteIfIndex.o_length == 0) continue; oct2ifname(&rp->ipv6RouteIfIndex, lifr.lifr_name, LIFNAMSIZ); if (ioctl(ifsock_v6, SIOCGLIFGROUPNAME, &lifr) == -1 || strlcpy(grname, lifr.lifr_groupname, LIFGRNAMSIZ) == 0) { continue; } /* * Rescan the list of routes for interface routes, and add the * above target to any interfaces in the same IPMP group. */ for (rp1 = buf; rp1 < endp; rp1++) { if (!(rp1->ipv6RouteInfo.re_ire_type & IRE_INTERFACE) || rp1->ipv6RouteIfIndex.o_length == 0) { continue; } oct2ifname(&rp1->ipv6RouteIfIndex, ifname, LIFNAMSIZ); (void) strlcpy(lifr.lifr_name, ifname, LIFNAMSIZ); if (ioctl(ifsock_v6, SIOCGLIFGROUPNAME, &lifr) != -1 && strcmp(lifr.lifr_groupname, grname) == 0) { router_add_common(AF_INET6, ifname, nexthop_v6); } } } } /* * Build a list of target routers, by scanning the routing tables. * It is assumed that interface routes exist, to reach the routers. */ static void init_router_targets(void) { struct target *tg; struct target *next_tg; struct phyint_instance *pii; struct phyint *pi; if (force_mcast) return; for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { pi = pii->pii_phyint; /* * Set tg_in_use to false only for router targets. */ if (!pii->pii_targets_are_routers) continue; for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) tg->tg_in_use = 0; } if (mibwalk(update_router_list) == -1) exit(1); for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { pi = pii->pii_phyint; if (!pii->pii_targets_are_routers) continue; for (tg = pii->pii_targets; tg != NULL; tg = next_tg) { next_tg = tg->tg_next; /* * If the group has failed, it's likely the route was * removed by an application affected by that failure. * In that case, we keep the target so that we can * reliably repair, at which point we'll refresh the * target list again. */ if (!tg->tg_in_use && !GROUP_FAILED(pi->pi_group)) target_delete(tg); } } } /* * Attempt to assign host targets to any interfaces that do not currently * have probe targets by sharing targets with other interfaces in the group. */ static void init_host_targets(void) { struct phyint_instance *pii; struct phyint_group *pg; for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { pg = pii->pii_phyint->pi_group; if (pg != phyint_anongroup && pii->pii_targets == NULL) dup_host_targets(pii); } } /* * Duplicate host targets from other phyints of the group to * the phyint instance 'desired_pii'. */ static void dup_host_targets(struct phyint_instance *desired_pii) { int af; struct phyint *pi; struct phyint_instance *pii; struct target *tg; assert(desired_pii->pii_phyint->pi_group != phyint_anongroup); af = desired_pii->pii_af; /* * For every phyint in the same group as desired_pii, check if * it has any host targets. If so add them to desired_pii. */ for (pi = desired_pii->pii_phyint; pi != NULL; pi = pi->pi_pgnext) { pii = PHYINT_INSTANCE(pi, af); /* * We know that we don't have targets on this phyint instance * since we have been called. But we still check for * pii_targets_are_routers because another phyint instance * could have router targets, since IFF_NOFAILOVER addresses * on different phyint instances may belong to different * subnets. */ if ((pii == NULL) || (pii == desired_pii) || pii->pii_targets_are_routers) continue; for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { target_create(desired_pii, tg->tg_address, _B_FALSE); } } } static void usage(char *cmd) { (void) fprintf(stderr, "usage: %s\n", cmd); } #define MPATHD_DEFAULT_FILE "/etc/default/mpathd" /* Get an option from the /etc/default/mpathd file */ static char * getdefault(char *name) { char namebuf[BUFSIZ]; char *value = NULL; if (defopen(MPATHD_DEFAULT_FILE) == 0) { char *cp; int flags; /* * ignore case */ flags = defcntl(DC_GETFLAGS, 0); TURNOFF(flags, DC_CASE); (void) defcntl(DC_SETFLAGS, flags); /* Add "=" to the name */ (void) strncpy(namebuf, name, sizeof (namebuf) - 2); (void) strncat(namebuf, "=", 2); if ((cp = defread(namebuf)) != NULL) value = strdup(cp); /* close */ (void) defopen((char *)NULL); } return (value); } /* * Command line options below */ boolean_t failback_enabled = _B_TRUE; /* failback enabled/disabled */ boolean_t track_all_phyints = _B_FALSE; /* track all IP interfaces */ static boolean_t adopt = _B_FALSE; static boolean_t foreground = _B_FALSE; int main(int argc, char *argv[]) { int i; int c; struct phyint *pi; struct phyint_instance *pii; char *value; argv0 = argv; /* Saved for re-exec on SIGHUP */ srandom(gethostid()); /* Initialize the random number generator */ /* * NOTE: The messages output by in.mpathd are not suitable for * translation, so we do not call textdomain(). */ (void) setlocale(LC_ALL, ""); /* * Get the user specified value of 'failure detection time' * from /etc/default/mpathd */ value = getdefault("FAILURE_DETECTION_TIME"); if (value != NULL) { user_failure_detection_time = (int)strtol((char *)value, NULL, 0); if (user_failure_detection_time <= 0) { user_failure_detection_time = FAILURE_DETECTION_TIME; logerr("Invalid failure detection time %s, assuming " "default of %d ms\n", value, user_failure_detection_time); } else if (user_failure_detection_time < MIN_FAILURE_DETECTION_TIME) { user_failure_detection_time = MIN_FAILURE_DETECTION_TIME; logerr("Too small failure detection time of %s, " "assuming minimum of %d ms\n", value, user_failure_detection_time); } free(value); } else { /* User has not specified the parameter, Use default value */ user_failure_detection_time = FAILURE_DETECTION_TIME; } /* * This gives the frequency at which probes will be sent. * When fdt ms elapses, we should be able to determine * whether 5 consecutive probes have failed or not. * 1 probe will be sent in every user_probe_interval ms, * randomly anytime in the (0.5 - 1.0) 2nd half of every * user_probe_interval. Thus when we send out probe 'n' we * can be sure that probe 'n - 2' is lost, if we have not * got the ack. (since the probe interval is > crtt). But * probe 'n - 1' may be a valid unacked probe, since the * time between 2 successive probes could be as small as * 0.5 * user_probe_interval. Hence the NUM_PROBE_FAILS + 2 */ user_probe_interval = user_failure_detection_time / (NUM_PROBE_FAILS + 2); /* * Get the user specified value of failback_enabled from * /etc/default/mpathd */ value = getdefault("FAILBACK"); if (value != NULL) { if (strcasecmp(value, "yes") == 0) failback_enabled = _B_TRUE; else if (strcasecmp(value, "no") == 0) failback_enabled = _B_FALSE; else logerr("Invalid value for FAILBACK %s\n", value); free(value); } else { failback_enabled = _B_TRUE; } /* * Get the user specified value of track_all_phyints from * /etc/default/mpathd. The sense is reversed in * TRACK_INTERFACES_ONLY_WITH_GROUPS. */ value = getdefault("TRACK_INTERFACES_ONLY_WITH_GROUPS"); if (value != NULL) { if (strcasecmp(value, "yes") == 0) track_all_phyints = _B_FALSE; else if (strcasecmp(value, "no") == 0) track_all_phyints = _B_TRUE; else logerr("Invalid value for " "TRACK_INTERFACES_ONLY_WITH_GROUPS %s\n", value); free(value); } else { track_all_phyints = _B_FALSE; } while ((c = getopt(argc, argv, "adD:ml")) != EOF) { switch (c) { case 'a': adopt = _B_TRUE; break; case 'm': force_mcast = _B_TRUE; break; case 'd': debug = D_ALL; foreground = _B_TRUE; break; case 'D': i = (int)strtol(optarg, NULL, 0); if (i == 0) { (void) fprintf(stderr, "Bad debug flags: %s\n", optarg); exit(1); } debug |= i; foreground = _B_TRUE; break; case 'l': /* * Turn off link state notification handling. * Undocumented command line flag, for debugging * purposes. */ handle_link_notifications = _B_FALSE; break; default: usage(argv[0]); exit(1); } } /* * The sockets for the loopback command interface should be listening * before we fork and exit in daemonize(). This way, whoever started us * can use the loopback interface as soon as they get a zero exit * status. */ lsock_v4 = setup_listener(AF_INET); lsock_v6 = setup_listener(AF_INET6); if (lsock_v4 < 0 && lsock_v6 < 0) { logerr("main: setup_listener failed for both IPv4 and IPv6\n"); exit(1); } if (!foreground) { if (!daemonize()) { logerr("cannot daemonize\n"); exit(EXIT_FAILURE); } initlog(); } /* * Initializations: * 1. Create ifsock* sockets. These are used for performing SIOC* * ioctls. We have 2 sockets 1 each for IPv4 and IPv6. * 2. Initialize a pipe for handling/recording signal events. * 3. Create the routing sockets, used for listening * to routing / interface changes. * 4. phyint_init() - Initialize physical interface state * (in mpd_tables.c). Must be done before creating interfaces, * which timer_init() does indirectly. * 5. Query kernel for route entry sizes (v4 and v6). * 6. timer_init() - Initialize timer related stuff * 7. initifs() - Initialize our database of all known interfaces * 8. init_router_targets() - Initialize our database of all known * router targets. */ ifsock_v4 = socket(AF_INET, SOCK_DGRAM, 0); if (ifsock_v4 < 0) { logperror("main: IPv4 socket open"); exit(1); } ifsock_v6 = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock_v6 < 0) { logperror("main: IPv6 socket open"); exit(1); } setup_eventpipe(); rtsock_v4 = setup_rtsock(AF_INET); rtsock_v6 = setup_rtsock(AF_INET6); if (phyint_init() == -1) { logerr("cannot initialize physical interface structures"); exit(1); } if (mibwalk(mib_get_constants) == -1) exit(1); timer_init(); initifs(); /* * If we're operating in "adopt" mode and no interfaces need to be * tracked, shut down (ifconfig(8) will restart us on demand if * interfaces are subsequently put into multipathing groups). */ if (adopt && phyint_instances == NULL) exit(0); /* * Main body. Keep listening for activity on any of the sockets * that we are monitoring and take appropriate action as necessary. * signals are also handled synchronously. */ for (;;) { if (poll(pollfds, pollfd_num, -1) < 0) { if (errno == EINTR) continue; logperror("main: poll"); exit(1); } for (i = 0; i < pollfd_num; i++) { if ((pollfds[i].fd == -1) || !(pollfds[i].revents & POLLIN)) continue; if (pollfds[i].fd == eventpipe_read) { in_signal(eventpipe_read); break; } if (pollfds[i].fd == rtsock_v4 || pollfds[i].fd == rtsock_v6) { process_rtsock(rtsock_v4, rtsock_v6); break; } for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { if (pollfds[i].fd == pii->pii_probe_sock) { if (pii->pii_af == AF_INET) in_data(pii); else in6_data(pii); break; } } for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pi->pi_notes != 0 && pollfds[i].fd == dlpi_fd(pi->pi_dh)) { (void) dlpi_recv(pi->pi_dh, NULL, NULL, NULL, NULL, 0, NULL); break; } } if (pollfds[i].fd == lsock_v4) loopback_cmd(lsock_v4, AF_INET); else if (pollfds[i].fd == lsock_v6) loopback_cmd(lsock_v6, AF_INET6); } } /* NOTREACHED */ return (EXIT_SUCCESS); } static int setup_listener(int af) { int sock; int on; int len; int ret; struct sockaddr_storage laddr; struct sockaddr_in *sin; struct sockaddr_in6 *sin6; struct in6_addr loopback_addr = IN6ADDR_LOOPBACK_INIT; assert(af == AF_INET || af == AF_INET6); sock = socket(af, SOCK_STREAM, 0); if (sock < 0) { logperror("setup_listener: socket"); exit(1); } on = 1; if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)) < 0) { logperror("setup_listener: setsockopt (SO_REUSEADDR)"); exit(1); } bzero(&laddr, sizeof (laddr)); laddr.ss_family = af; if (af == AF_INET) { sin = (struct sockaddr_in *)&laddr; sin->sin_port = htons(MPATHD_PORT); sin->sin_addr.s_addr = htonl(INADDR_LOOPBACK); len = sizeof (struct sockaddr_in); } else { sin6 = (struct sockaddr_in6 *)&laddr; sin6->sin6_port = htons(MPATHD_PORT); sin6->sin6_addr = loopback_addr; len = sizeof (struct sockaddr_in6); } ret = bind(sock, (struct sockaddr *)&laddr, len); if (ret < 0) { if (errno == EADDRINUSE) { /* * Another instance of mpathd may be already active. */ logerr("main: is another instance of in.mpathd " "already active?\n"); exit(1); } else { (void) close(sock); return (-1); } } if (listen(sock, 30) < 0) { logperror("main: listen"); exit(1); } if (poll_add(sock) == -1) { (void) close(sock); exit(1); } return (sock); } /* * Table of commands and their expected size; used by loopback_cmd(). */ static struct { const char *name; unsigned int size; } commands[] = { { "MI_PING", sizeof (uint32_t) }, { "MI_OFFLINE", sizeof (mi_offline_t) }, { "MI_UNDO_OFFLINE", sizeof (mi_undo_offline_t) }, { "MI_QUERY", sizeof (mi_query_t) } }; /* * Commands received over the loopback interface come here (via libipmp). */ static void loopback_cmd(int sock, int family) { int newfd; ssize_t len; boolean_t is_priv = _B_FALSE; struct sockaddr_storage peer; struct sockaddr_in *peer_sin; struct sockaddr_in6 *peer_sin6; socklen_t peerlen; union mi_commands mpi; char abuf[INET6_ADDRSTRLEN]; uint_t cmd; int retval; peerlen = sizeof (peer); newfd = accept(sock, (struct sockaddr *)&peer, &peerlen); if (newfd < 0) { logperror("loopback_cmd: accept"); return; } switch (family) { case AF_INET: /* * Validate the address and port to make sure that * non privileged processes don't connect and start * talking to us. */ if (peerlen != sizeof (struct sockaddr_in)) { logerr("loopback_cmd: AF_INET peerlen %d\n", peerlen); (void) close(newfd); return; } peer_sin = (struct sockaddr_in *)&peer; is_priv = ntohs(peer_sin->sin_port) < IPPORT_RESERVED; (void) inet_ntop(AF_INET, &peer_sin->sin_addr.s_addr, abuf, sizeof (abuf)); if (ntohl(peer_sin->sin_addr.s_addr) != INADDR_LOOPBACK) { logerr("Attempt to connect from addr %s port %d\n", abuf, ntohs(peer_sin->sin_port)); (void) close(newfd); return; } break; case AF_INET6: if (peerlen != sizeof (struct sockaddr_in6)) { logerr("loopback_cmd: AF_INET6 peerlen %d\n", peerlen); (void) close(newfd); return; } /* * Validate the address and port to make sure that * non privileged processes don't connect and start * talking to us. */ peer_sin6 = (struct sockaddr_in6 *)&peer; is_priv = ntohs(peer_sin6->sin6_port) < IPPORT_RESERVED; (void) inet_ntop(AF_INET6, &peer_sin6->sin6_addr, abuf, sizeof (abuf)); if (!IN6_IS_ADDR_LOOPBACK(&peer_sin6->sin6_addr)) { logerr("Attempt to connect from addr %s port %d\n", abuf, ntohs(peer_sin6->sin6_port)); (void) close(newfd); return; } break; default: logdebug("loopback_cmd: family %d\n", family); (void) close(newfd); return; } /* * The sizeof the 'mpi' buffer corresponds to the maximum size of * all supported commands */ len = read(newfd, &mpi, sizeof (mpi)); /* * In theory, we can receive any sized message for a stream socket, * but we don't expect that to happen for a small message over a * loopback connection. */ if (len < sizeof (uint32_t)) { logerr("loopback_cmd: bad command format or read returns " "partial data %d\n", len); (void) close(newfd); return; } cmd = mpi.mi_command; if (cmd >= MI_NCMD) { logerr("loopback_cmd: unknown command id `%d'\n", cmd); (void) close(newfd); return; } /* * Only MI_PING and MI_QUERY can come from unprivileged sources. */ if (!is_priv && (cmd != MI_QUERY && cmd != MI_PING)) { logerr("Unprivileged request from %s for privileged " "command %s\n", abuf, commands[cmd].name); (void) close(newfd); return; } if (len < commands[cmd].size) { logerr("loopback_cmd: short %s command (expected %d, got %d)\n", commands[cmd].name, commands[cmd].size, len); (void) close(newfd); return; } retval = process_cmd(newfd, &mpi); if (retval != IPMP_SUCCESS) { logerr("failed processing %s: %s\n", commands[cmd].name, ipmp_errmsg(retval)); } (void) close(newfd); } /* * Process the commands received via libipmp. */ static unsigned int process_cmd(int newfd, union mi_commands *mpi) { struct phyint *pi; struct mi_offline *mio; struct mi_undo_offline *miu; unsigned int retval; switch (mpi->mi_command) { case MI_PING: return (send_result(newfd, IPMP_SUCCESS, 0)); case MI_OFFLINE: mio = &mpi->mi_ocmd; pi = phyint_lookup(mio->mio_ifname); if (pi == NULL) return (send_result(newfd, IPMP_EUNKIF, 0)); retval = phyint_offline(pi, mio->mio_min_redundancy); if (retval == IPMP_FAILURE) return (send_result(newfd, IPMP_FAILURE, errno)); return (send_result(newfd, retval, 0)); case MI_UNDO_OFFLINE: miu = &mpi->mi_ucmd; pi = phyint_lookup(miu->miu_ifname); if (pi == NULL) return (send_result(newfd, IPMP_EUNKIF, 0)); retval = phyint_undo_offline(pi); if (retval == IPMP_FAILURE) return (send_result(newfd, IPMP_FAILURE, errno)); return (send_result(newfd, retval, 0)); case MI_QUERY: return (process_query(newfd, &mpi->mi_qcmd)); default: break; } return (send_result(newfd, IPMP_EPROTO, 0)); } /* * Process the query request pointed to by `miq' and send a reply on file * descriptor `fd'. Returns an IPMP error code. */ static unsigned int process_query(int fd, mi_query_t *miq) { ipmp_addrinfo_t *adinfop; ipmp_addrinfolist_t *adlp; ipmp_groupinfo_t *grinfop; ipmp_groupinfolist_t *grlp; ipmp_grouplist_t *grlistp; ipmp_ifinfo_t *ifinfop; ipmp_ifinfolist_t *iflp; ipmp_snap_t *snap; unsigned int retval; switch (miq->miq_inforeq) { case IPMP_ADDRINFO: retval = getgraddrinfo(miq->miq_grname, &miq->miq_addr, &adinfop); if (retval != IPMP_SUCCESS) return (send_result(fd, retval, errno)); retval = send_result(fd, IPMP_SUCCESS, 0); if (retval == IPMP_SUCCESS) retval = send_addrinfo(fd, adinfop); ipmp_freeaddrinfo(adinfop); return (retval); case IPMP_GROUPLIST: retval = getgrouplist(&grlistp); if (retval != IPMP_SUCCESS) return (send_result(fd, retval, errno)); retval = send_result(fd, IPMP_SUCCESS, 0); if (retval == IPMP_SUCCESS) retval = send_grouplist(fd, grlistp); ipmp_freegrouplist(grlistp); return (retval); case IPMP_GROUPINFO: miq->miq_grname[LIFGRNAMSIZ - 1] = '\0'; retval = getgroupinfo(miq->miq_grname, &grinfop); if (retval != IPMP_SUCCESS) return (send_result(fd, retval, errno)); retval = send_result(fd, IPMP_SUCCESS, 0); if (retval == IPMP_SUCCESS) retval = send_groupinfo(fd, grinfop); ipmp_freegroupinfo(grinfop); return (retval); case IPMP_IFINFO: miq->miq_ifname[LIFNAMSIZ - 1] = '\0'; retval = getifinfo(miq->miq_ifname, &ifinfop); if (retval != IPMP_SUCCESS) return (send_result(fd, retval, errno)); retval = send_result(fd, IPMP_SUCCESS, 0); if (retval == IPMP_SUCCESS) retval = send_ifinfo(fd, ifinfop); ipmp_freeifinfo(ifinfop); return (retval); case IPMP_SNAP: /* * Before taking the snapshot, sync with the kernel. */ initifs(); retval = getsnap(&snap); if (retval != IPMP_SUCCESS) return (send_result(fd, retval, errno)); retval = send_result(fd, IPMP_SUCCESS, 0); if (retval != IPMP_SUCCESS) goto out; retval = send_grouplist(fd, snap->sn_grlistp); if (retval != IPMP_SUCCESS) goto out; retval = ipmp_writetlv(fd, IPMP_IFCNT, sizeof (uint32_t), &snap->sn_nif); if (retval != IPMP_SUCCESS) goto out; iflp = snap->sn_ifinfolistp; for (; iflp != NULL; iflp = iflp->ifl_next) { retval = send_ifinfo(fd, iflp->ifl_ifinfop); if (retval != IPMP_SUCCESS) goto out; } retval = ipmp_writetlv(fd, IPMP_GROUPCNT, sizeof (uint32_t), &snap->sn_ngroup); if (retval != IPMP_SUCCESS) goto out; grlp = snap->sn_grinfolistp; for (; grlp != NULL; grlp = grlp->grl_next) { retval = send_groupinfo(fd, grlp->grl_grinfop); if (retval != IPMP_SUCCESS) goto out; } retval = ipmp_writetlv(fd, IPMP_ADDRCNT, sizeof (uint32_t), &snap->sn_naddr); if (retval != IPMP_SUCCESS) goto out; adlp = snap->sn_adinfolistp; for (; adlp != NULL; adlp = adlp->adl_next) { retval = send_addrinfo(fd, adlp->adl_adinfop); if (retval != IPMP_SUCCESS) goto out; } out: ipmp_snap_free(snap); return (retval); default: break; } return (send_result(fd, IPMP_EPROTO, 0)); } /* * Send the group information pointed to by `grinfop' on file descriptor `fd'. * Returns an IPMP error code. */ static unsigned int send_groupinfo(int fd, ipmp_groupinfo_t *grinfop) { ipmp_iflist_t *iflistp = grinfop->gr_iflistp; ipmp_addrlist_t *adlistp = grinfop->gr_adlistp; ipmp_groupinfo_xfer_t grxfer; unsigned int retval; /* * We can't directly transfer an ipmp_groupinfo_t due to the embedded * pointers to ipmp_iflist_t and ipmp_addr_list_t. Copy the data over * to a temporary transfer structure that doesn't have these embedded * pointers. */ memset(&grxfer, 0, sizeof (grxfer)); grxfer.grx_sig = grinfop->gr_sig; grxfer.grx_state = grinfop->gr_state; grxfer.grx_fdt = grinfop->gr_fdt; memcpy(grxfer.grx_name, grinfop->gr_name, sizeof (grxfer.grx_name)); memcpy(grxfer.grx_ifname, grinfop->gr_ifname, sizeof (grxfer.grx_ifname)); memcpy(grxfer.grx_m4ifname, grinfop->gr_m4ifname, sizeof (grxfer.grx_m4ifname)); memcpy(grxfer.grx_m6ifname, grinfop->gr_m6ifname, sizeof (grxfer.grx_m6ifname)); memcpy(grxfer.grx_bcifname, grinfop->gr_bcifname, sizeof (grxfer.grx_bcifname)); retval = ipmp_writetlv(fd, IPMP_GROUPINFO, sizeof (grxfer), &grxfer); if (retval != IPMP_SUCCESS) return (retval); retval = ipmp_writetlv(fd, IPMP_IFLIST, IPMP_IFLIST_SIZE(iflistp->il_nif), iflistp); if (retval != IPMP_SUCCESS) return (retval); return (ipmp_writetlv(fd, IPMP_ADDRLIST, IPMP_ADDRLIST_SIZE(adlistp->al_naddr), adlistp)); } /* * Send the interface information pointed to by `ifinfop' on file descriptor * `fd'. Returns an IPMP error code. */ static unsigned int send_ifinfo(int fd, ipmp_ifinfo_t *ifinfop) { ipmp_addrlist_t *adlist4p = ifinfop->if_targinfo4.it_targlistp; ipmp_addrlist_t *adlist6p = ifinfop->if_targinfo6.it_targlistp; ipmp_ifinfo_xfer_t ifxfer; unsigned int retval; /* * We can't directly tranfer an ipmp_ifinfo_t due to the embedded * ipmp_addrlist_t pointer in if_targinfo_t. Copy the data over to * a temporary transfer structure that doesn't have that embedded * pointer. */ memset(&ifxfer, 0, sizeof (ifxfer)); ifxfer.ifx_state = ifinfop->if_state; ifxfer.ifx_type = ifinfop->if_type; ifxfer.ifx_linkstate = ifinfop->if_linkstate; ifxfer.ifx_probestate = ifinfop->if_probestate; ifxfer.ifx_flags = ifinfop->if_flags; ifxfer.ifx_targinfo4.itx_testaddr = ifinfop->if_targinfo4.it_testaddr; ifxfer.ifx_targinfo4.itx_targmode = ifinfop->if_targinfo4.it_targmode; ifxfer.ifx_targinfo6.itx_testaddr = ifinfop->if_targinfo6.it_testaddr; ifxfer.ifx_targinfo6.itx_targmode = ifinfop->if_targinfo6.it_targmode; memcpy(ifxfer.ifx_name, ifinfop->if_name, sizeof (ifxfer.ifx_name)); memcpy(ifxfer.ifx_group, ifinfop->if_group, sizeof (ifxfer.ifx_group)); memcpy(ifxfer.ifx_targinfo4.itx_name, ifinfop->if_targinfo4.it_name, sizeof (ifxfer.ifx_targinfo4.itx_name)); memcpy(ifxfer.ifx_targinfo6.itx_name, ifinfop->if_targinfo6.it_name, sizeof (ifxfer.ifx_targinfo6.itx_name)); retval = ipmp_writetlv(fd, IPMP_IFINFO, sizeof (ifxfer), &ifxfer); if (retval != IPMP_SUCCESS) return (retval); retval = ipmp_writetlv(fd, IPMP_ADDRLIST, IPMP_ADDRLIST_SIZE(adlist4p->al_naddr), adlist4p); if (retval != IPMP_SUCCESS) return (retval); return (ipmp_writetlv(fd, IPMP_ADDRLIST, IPMP_ADDRLIST_SIZE(adlist6p->al_naddr), adlist6p)); } /* * Send the address information pointed to by `adinfop' on file descriptor * `fd'. Returns an IPMP error code. */ static unsigned int send_addrinfo(int fd, ipmp_addrinfo_t *adinfop) { return (ipmp_writetlv(fd, IPMP_ADDRINFO, sizeof (*adinfop), adinfop)); } /* * Send the group list pointed to by `grlistp' on file descriptor `fd'. * Returns an IPMP error code. */ static unsigned int send_grouplist(int fd, ipmp_grouplist_t *grlistp) { return (ipmp_writetlv(fd, IPMP_GROUPLIST, IPMP_GROUPLIST_SIZE(grlistp->gl_ngroup), grlistp)); } /* * Initialize an mi_result_t structure using `error' and `syserror' and * send it on file descriptor `fd'. Returns an IPMP error code. */ static unsigned int send_result(int fd, unsigned int error, int syserror) { mi_result_t me; me.me_mpathd_error = error; if (error == IPMP_FAILURE) me.me_sys_error = syserror; else me.me_sys_error = 0; return (ipmp_write(fd, &me, sizeof (me))); } /* * Daemonize the process. */ static boolean_t daemonize(void) { switch (fork()) { case -1: return (_B_FALSE); case 0: /* * Lose our controlling terminal, and become both a session * leader and a process group leader. */ if (setsid() == -1) return (_B_FALSE); /* * Under POSIX, a session leader can accidentally (through * open(2)) acquire a controlling terminal if it does not * have one. Just to be safe, fork() again so we are not a * session leader. */ switch (fork()) { case -1: return (_B_FALSE); case 0: (void) chdir("/"); (void) umask(022); (void) fdwalk(closefunc, NULL); break; default: _exit(EXIT_SUCCESS); } break; default: _exit(EXIT_SUCCESS); } return (_B_TRUE); } /* * The parent has created some fds before forking on purpose, keep them open. */ static int closefunc(void *not_used, int fd) { if (fd != lsock_v4 && fd != lsock_v6) (void) close(fd); return (0); } /* LOGGER */ #include /* * Logging routines. All routines log to syslog, unless the daemon is * running in the foreground, in which case the logging goes to stderr. * * The following routines are available: * * logdebug(): A printf-like function for outputting debug messages * (messages at LOG_DEBUG) that are only of use to developers. * * logtrace(): A printf-like function for outputting tracing messages * (messages at LOG_INFO) from the daemon. This is typically used * to log the receipt of interesting network-related conditions. * * 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. */ static int logging = 0; static void initlog(void) { logging++; openlog("in.mpathd", LOG_PID, LOG_DAEMON); } /* PRINTFLIKE2 */ void logmsg(int pri, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (logging) vsyslog(pri, fmt, ap); else (void) vfprintf(stderr, fmt, ap); va_end(ap); } /* PRINTFLIKE1 */ void logperror(const char *str) { if (logging) syslog(LOG_ERR, "%s: %m\n", str); else (void) fprintf(stderr, "%s: %s\n", str, strerror(errno)); } void logperror_pii(struct phyint_instance *pii, const char *str) { if (logging) { syslog(LOG_ERR, "%s (%s %s): %m\n", str, AF_STR(pii->pii_af), pii->pii_phyint->pi_name); } else { (void) fprintf(stderr, "%s (%s %s): %s\n", str, AF_STR(pii->pii_af), pii->pii_phyint->pi_name, strerror(errno)); } } void logperror_li(struct logint *li, const char *str) { struct phyint_instance *pii = li->li_phyint_inst; if (logging) { syslog(LOG_ERR, "%s (%s %s): %m\n", str, AF_STR(pii->pii_af), li->li_name); } else { (void) fprintf(stderr, "%s (%s %s): %s\n", str, AF_STR(pii->pii_af), li->li_name, strerror(errno)); } } void close_probe_socket(struct phyint_instance *pii, boolean_t polled) { if (polled) (void) poll_remove(pii->pii_probe_sock); (void) close(pii->pii_probe_sock); pii->pii_probe_sock = -1; pii->pii_basetime_inited = 0; } boolean_t addrlist_add(addrlist_t **addrsp, const char *name, uint64_t flags, struct sockaddr_storage *ssp) { addrlist_t *addrp; if ((addrp = malloc(sizeof (addrlist_t))) == NULL) return (_B_FALSE); (void) strlcpy(addrp->al_name, name, LIFNAMSIZ); addrp->al_flags = flags; addrp->al_addr = *ssp; addrp->al_next = *addrsp; *addrsp = addrp; return (_B_TRUE); } void addrlist_free(addrlist_t **addrsp) { addrlist_t *addrp, *next_addrp; for (addrp = *addrsp; addrp != NULL; addrp = next_addrp) { next_addrp = addrp->al_next; free(addrp); } *addrsp = NULL; } /* * Send down a T_OPTMGMT_REQ to ip asking for all data in the various * tables defined by mib2.h. Pass the table information returned to the * supplied function. */ static int mibwalk(void (*proc)(mib_item_t *)) { mib_item_t *head_item = NULL; mib_item_t *last_item = NULL; mib_item_t *tmp; struct strbuf ctlbuf, databuf; int flags; int rval; uintptr_t buf[512 / sizeof (uintptr_t)]; struct T_optmgmt_req *tor = (struct T_optmgmt_req *)buf; struct T_optmgmt_ack *toa = (struct T_optmgmt_ack *)buf; struct T_error_ack *tea = (struct T_error_ack *)buf; struct opthdr *req, *optp; int status = -1; if (mibfd == -1) { if ((mibfd = open("/dev/ip", O_RDWR)) < 0) { logperror("mibwalk(): ip open"); return (status); } } tor->PRIM_type = T_SVR4_OPTMGMT_REQ; tor->OPT_offset = sizeof (struct T_optmgmt_req); tor->OPT_length = sizeof (struct opthdr); tor->MGMT_flags = T_CURRENT; /* * Note: we use the special level value below so that IP will return * us information concerning IRE_MARK_TESTHIDDEN routes. */ req = (struct opthdr *)&tor[1]; req->level = EXPER_IP_AND_ALL_IRES; req->name = 0; req->len = 0; ctlbuf.buf = (char *)&buf; ctlbuf.len = tor->OPT_length + tor->OPT_offset; if (putmsg(mibfd, &ctlbuf, NULL, 0) == -1) { logperror("mibwalk(): putmsg(ctl)"); return (status); } /* * The response consists of multiple T_OPTMGMT_ACK msgs, 1 msg for * each table defined in mib2.h. Each T_OPTMGMT_ACK msg contains * a control and data part. The control part contains a struct * T_optmgmt_ack followed by a struct opthdr. The 'opthdr' identifies * the level, name and length of the data in the data part. The * data part contains the actual table data. The last message * is an end-of-data (EOD), consisting of a T_OPTMGMT_ACK and a * single option with zero optlen. */ for (;;) { errno = flags = 0; ctlbuf.maxlen = sizeof (buf); rval = getmsg(mibfd, &ctlbuf, NULL, &flags); if (rval & MORECTL || rval < 0) { if (errno == EINTR) continue; logerr("mibwalk(): getmsg(ctl) ret: %d err: %d\n", rval, errno); goto error; } if (ctlbuf.len < sizeof (t_scalar_t)) { logerr("mibwalk(): ctlbuf.len %d\n", ctlbuf.len); goto error; } switch (toa->PRIM_type) { case T_ERROR_ACK: if (ctlbuf.len < sizeof (struct T_error_ack)) { logerr("mibwalk(): T_ERROR_ACK ctlbuf " "too short: %d\n", ctlbuf.len); goto error; } logerr("mibwalk(): T_ERROR_ACK: TLI_err = 0x%lx: %s\n" " UNIX_err = 0x%lx\n", tea->TLI_error, t_strerror(tea->TLI_error), tea->UNIX_error); goto error; case T_OPTMGMT_ACK: optp = (struct opthdr *)&toa[1]; if (ctlbuf.len < (sizeof (struct T_optmgmt_ack) + sizeof (struct opthdr))) { logerr("mibwalk(): T_OPTMGMT_ACK ctlbuf too " "short: %d\n", ctlbuf.len); goto error; } if (toa->MGMT_flags != T_SUCCESS) { logerr("mibwalk(): MGMT_flags != T_SUCCESS: " "0x%lx\n", toa->MGMT_flags); goto error; } break; default: goto error; } /* The following assert also implies MGMT_flags == T_SUCCESS */ assert(toa->PRIM_type == T_OPTMGMT_ACK); /* * We have reached the end of this T_OPTMGMT_ACK * message. If this is the last message i.e EOD, * break, else process the next T_OPTMGMT_ACK msg. */ if (rval == 0) { if (optp->len == 0 && optp->name == 0 && optp->level == 0) { /* This is the EOD message. */ break; } /* Not EOD but no data to retrieve */ continue; } /* * We should only be here if MOREDATA was set. * Allocate an empty mib_item_t and link into the list * of MIB items. */ if ((tmp = malloc(sizeof (*tmp))) == NULL) { logperror("mibwalk(): malloc() failed."); goto error; } if (last_item != NULL) last_item->mi_next = tmp; else head_item = tmp; last_item = tmp; last_item->mi_next = NULL; last_item->mi_opthdr = *optp; last_item->mi_valp = malloc(optp->len); if (last_item->mi_valp == NULL) { logperror("mibwalk(): malloc() failed."); goto error; } databuf.maxlen = last_item->mi_opthdr.len; databuf.buf = (char *)last_item->mi_valp; databuf.len = 0; /* Retrieve the actual MIB data */ for (;;) { flags = 0; if ((rval = getmsg(mibfd, NULL, &databuf, &flags)) != 0) { if (rval < 0 && errno == EINTR) continue; /* * We shouldn't get MOREDATA here so treat that * as an error. */ logperror("mibwalk(): getmsg(data)"); goto error; } break; } } status = 0; /* Pass the accumulated MIB data to the supplied function pointer */ (*proc)(head_item); error: while (head_item != NULL) { tmp = head_item; head_item = tmp->mi_next; free(tmp->mi_valp); free(tmp); } return (status); } /* * Parse the supplied mib2 information to get the size of routing table * entries. This is needed when running in a branded zone where the * Solaris application environment and the Solaris kernel may not be the * the same release version. */ static void mib_get_constants(mib_item_t *item) { mib2_ip_t *ipv4; mib2_ipv6IfStatsEntry_t *ipv6; for (; item != NULL; item = item->mi_next) { if (item->mi_opthdr.name != 0) continue; if (item->mi_opthdr.level == MIB2_IP) { ipv4 = (mib2_ip_t *)item->mi_valp; ipRouteEntrySize = ipv4->ipRouteEntrySize; } else if (item->mi_opthdr.level == MIB2_IP6) { ipv6 = (mib2_ipv6IfStatsEntry_t *)item->mi_valp; ipv6RouteEntrySize = ipv6->ipv6RouteEntrySize; } } } /* * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * 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. */ #include "mpd_defs.h" #include "mpd_tables.h" /* * Probe types for probe() */ #define PROBE_UNI 0x1234 /* Unicast probe packet */ #define PROBE_MULTI 0x5678 /* Multicast probe packet */ #define PROBE_RTT 0x9abc /* RTT only probe packet */ #define MSEC_PERMIN (60 * MILLISEC) /* Number of milliseconds in a minute */ /* * Format of probe / probe response packets. This is an ICMP Echo request * or ICMP Echo reply. Packet format is same for both IPv4 and IPv6 */ struct pr_icmp { uint8_t pr_icmp_type; /* type field */ uint8_t pr_icmp_code; /* code field */ uint16_t pr_icmp_cksum; /* checksum field */ uint16_t pr_icmp_id; /* Identification */ uint16_t pr_icmp_seq; /* sequence number */ uint64_t pr_icmp_timestamp; /* Time stamp (in ns) */ uint32_t pr_icmp_mtype; /* Message type */ }; static struct in6_addr all_nodes_mcast_v6 = { { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 } }; static struct in_addr all_nodes_mcast_v4 = { { { 0xe0, 0x0, 0x0, 0x1 } } }; static hrtime_t last_fdt_bumpup_time; /* When FDT was bumped up last */ static void *find_ancillary(struct msghdr *msg, int cmsg_level, int cmsg_type); static void pi_set_crtt(struct target *tg, int64_t m, boolean_t is_probe_uni); static void incoming_echo_reply(struct phyint_instance *pii, struct pr_icmp *reply, struct in6_addr fromaddr, struct timeval *recv_tvp); static void incoming_rtt_reply(struct phyint_instance *pii, struct pr_icmp *reply, struct in6_addr fromaddr); static void incoming_mcast_reply(struct phyint_instance *pii, struct pr_icmp *reply, struct in6_addr fromaddr); static boolean_t check_pg_crtt_improved(struct phyint_group *pg); static boolean_t check_pii_crtt_improved(struct phyint_instance *pii); static boolean_t check_exception_target(struct phyint_instance *pii, struct target *target); static void probe_fail_info(struct phyint_instance *pii, struct target *cur_tg, struct probe_fail_count *pfinfo); static void probe_success_info(struct phyint_instance *pii, struct target *cur_tg, struct probe_success_count *psinfo); static boolean_t phyint_repaired(struct phyint *pi); static boolean_t highest_ack_tg(uint16_t seq, struct target *tg); static int in_cksum(ushort_t *addr, int len); static void reset_snxt_basetimes(void); static int ns2ms(int64_t ns); static int64_t tv2ns(struct timeval *); /* * CRTT - Conservative Round Trip Time Estimate * Probe success - A matching probe reply received before CRTT ms has elapsed * after sending the probe. * Probe failure - No probe reply received and more than CRTT ms has elapsed * after sending the probe. * * TLS - Time last success. Most recent probe ack received at this time. * TFF - Time first fail. The time of the earliest probe failure in * a consecutive series of probe failures. * NUM_PROBE_REPAIRS - Number of consecutive successful probes required * before declaring phyint repair. * NUM_PROBE_FAILS - Number of consecutive probe failures required to * declare a phyint failure. * * Phyint state diagram * * The state of a phyint that is capable of being probed, is completely * specified by the 3-tuple . * * A phyint starts in either PI_RUNNING or PI_OFFLINE, depending on whether * IFF_OFFLINE is set. If the phyint is also configured with a test address * (the common case) and probe targets, then a phyint must also successfully * be able to send and receive probes in order to remain in the PI_RUNNING * state (otherwise, it transitions to PI_FAILED). * * Further, if a PI_RUNNING phyint is configured with a test address but is * unable to find any probe targets, it will transition to the PI_NOTARGETS * state, which indicates that the link is apparently functional but that * in.mpathd is unable to send probes to verify functionality (in this case, * in.mpathd makes the optimistic assumption that the interface is working * correctly and thus does not mark the interface FAILED, but reports it as * IPMP_IF_UNKNOWN through the async events and query interfaces). * * At any point, a phyint may be administratively marked offline via if_mpadm. * In this case, the interface always transitions to PI_OFFLINE, regardless * of its previous state. When the interface is later brought back online, * in.mpathd acts as if the interface is new (and thus it transitions to * PI_RUNNING or PI_FAILED based on the status of the link and the result of * its probes, if probes are sent). * * pi_state - PI_RUNNING or PI_FAILED * PI_RUNNING: The failure detection logic says the phyint is good. * PI_FAILED: The failure detection logic says the phyint has failed. * * pg_state - PG_OK, PG_DEGRADED, or PG_FAILED. * PG_OK: All interfaces in the group are OK. * PG_DEGRADED: Some interfaces in the group are unusable. * PG_FAILED: All interfaces in the group are unusable. * * In the case of router targets, we assume that the current list of * targets obtained from the routing table, is still valid, so the * phyint stat is PI_FAILED. In the case of host targets, we delete the * list of targets, and multicast to the all hosts, to reconstruct the * target list. So the phyints are in the PI_NOTARGETS state. * * I - value of (pi_flags & IFF_INACTIVE) * IFF_INACTIVE: This phyint will not send or receive packets. * Usually, inactive is tied to standby interfaces that are not yet * needed (e.g., no non-standby interfaces in the group have failed). * When failback has been disabled (FAILBACK=no configured), phyint can * also be a non-STANDBY. In this case IFF_INACTIVE is set when phyint * subsequently recovers after a failure. * * Not all 9 possible combinations of the above 3-tuple are possible. * * I is tracked by IP. pi_state is tracked by mpathd. * * pi_state state machine * --------------------------------------------------------------------------- * Event State New State * Action: * --------------------------------------------------------------------------- * IP interface failure (PI_RUNNING, I == 0) -> (PI_FAILED, I == 0) * detection : set IFF_FAILED on this phyint * * IP interface failure (PI_RUNNING, I == 1) -> (PI_FAILED, I == 0) * detection : set IFF_FAILED on this phyint * * IP interface repair (PI_FAILED, I == 0, FAILBACK=yes) * detection -> (PI_RUNNING, I == 0) * : clear IFF_FAILED on this phyint * * IP interface repair (PI_FAILED, I == 0, FAILBACK=no) * detection -> (PI_RUNNING, I == 1) * : clear IFF_FAILED on this phyint * : if failback is disabled set I == 1 * * Group failure (perform on all phyints in the group) * detection PI_RUNNING PI_FAILED * (Router targets) : set IFF_FAILED * * Group failure (perform on all phyints in the group) * detection PI_RUNNING PI_NOTARGETS * (Host targets) : set IFF_FAILED * : delete the target list on all phyints * --------------------------------------------------------------------------- */ struct probes_missed probes_missed; /* * Compose and transmit an ICMP ECHO REQUEST packet. The IP header * will be added on by the kernel. The id field identifies this phyint. * and the sequence number is an increasing (modulo 2^^16) integer. The data * portion holds the time value when the packet is sent. On echo this is * extracted to compute the round-trip time. Three different types of * probe packets are used. * * PROBE_UNI: This type is used to do failure detection / failure recovery * and RTT calculation. PROBE_UNI probes are spaced apart in time, * not less than the current CRTT. pii_probes[] stores data * about these probes. These packets consume sequence number space. * * PROBE_RTT: This type is used to make only rtt measurements. Normally these * are not used. Under heavy network load, the rtt may go up very high, * due to a spike, or may appear to go high, due to extreme scheduling * delays. Once the network stress is removed, mpathd takes long time to * recover, because the probe_interval is already high, and it takes * a long time to send out sufficient number of probes to bring down the * rtt. To avoid this problem, PROBE_RTT probes are sent out every * user_probe_interval ms. and will cause only rtt updates. These packets * do not consume sequence number space nor is information about these * packets stored in the pii_probes[] * * PROBE_MULTI: This type is only used to construct a list of targets, when * no targets are known. The packet is multicast to the all hosts addr. */ static void probe(struct phyint_instance *pii, uint_t probe_type, hrtime_t start_hrtime) { hrtime_t sent_hrtime; struct timeval sent_tv; struct pr_icmp probe_pkt; /* Probe packet */ struct sockaddr_storage targ; /* target address */ uint_t targaddrlen; /* targed address length */ int pr_ndx; /* probe index in pii->pii_probes[] */ boolean_t sent = _B_FALSE; int rval; if (debug & D_TARGET) { logdebug("probe(%s %s %d %lld)\n", AF_STR(pii->pii_af), pii->pii_name, probe_type, start_hrtime); } assert(pii->pii_probe_sock != -1); assert(probe_type == PROBE_UNI || probe_type == PROBE_MULTI || probe_type == PROBE_RTT); probe_pkt.pr_icmp_type = (pii->pii_af == AF_INET) ? ICMP_ECHO_REQUEST : ICMP6_ECHO_REQUEST; probe_pkt.pr_icmp_code = 0; probe_pkt.pr_icmp_cksum = 0; probe_pkt.pr_icmp_seq = htons(pii->pii_snxt); /* * Since there is no need to do arithmetic on the icmpid, * (only equality check is done) pii_icmpid is stored in * network byte order at initialization itself. */ probe_pkt.pr_icmp_id = pii->pii_icmpid; probe_pkt.pr_icmp_timestamp = htonll(start_hrtime); probe_pkt.pr_icmp_mtype = htonl(probe_type); /* * If probe_type is PROBE_MULTI, this packet will be multicast to * the all hosts address. Otherwise it is unicast to the next target. */ assert(probe_type == PROBE_MULTI || ((pii->pii_target_next != NULL) && pii->pii_rtt_target_next != NULL)); bzero(&targ, sizeof (targ)); targ.ss_family = pii->pii_af; if (pii->pii_af == AF_INET6) { struct in6_addr *addr6; addr6 = &((struct sockaddr_in6 *)&targ)->sin6_addr; targaddrlen = sizeof (struct sockaddr_in6); if (probe_type == PROBE_MULTI) { *addr6 = all_nodes_mcast_v6; } else if (probe_type == PROBE_UNI) { *addr6 = pii->pii_target_next->tg_address; } else { /* type is PROBE_RTT */ *addr6 = pii->pii_rtt_target_next->tg_address; } } else { struct in_addr *addr4; addr4 = &((struct sockaddr_in *)&targ)->sin_addr; targaddrlen = sizeof (struct sockaddr_in); if (probe_type == PROBE_MULTI) { *addr4 = all_nodes_mcast_v4; } else if (probe_type == PROBE_UNI) { IN6_V4MAPPED_TO_INADDR( &pii->pii_target_next->tg_address, addr4); } else { /* type is PROBE_RTT */ IN6_V4MAPPED_TO_INADDR( &pii->pii_rtt_target_next->tg_address, addr4); } /* * Compute the IPv4 icmp checksum. Does not cover the IP header. */ probe_pkt.pr_icmp_cksum = in_cksum((ushort_t *)&probe_pkt, (int)sizeof (probe_pkt)); } /* * Use the current time as the time we sent. Not atomic, but the best * we can do from here. */ sent_hrtime = gethrtime(); (void) gettimeofday(&sent_tv, NULL); rval = sendto(pii->pii_probe_sock, &probe_pkt, sizeof (probe_pkt), 0, (struct sockaddr *)&targ, targaddrlen); /* * If the send would block, this may either be transient or a hang in a * lower layer. We pretend the probe was actually sent, the daemon will * not see a reply to the probe and will fail the interface if normal * failure detection criteria are met. */ if (rval == sizeof (probe_pkt) || (rval == -1 && errno == EWOULDBLOCK)) { sent = _B_TRUE; } else { logperror_pii(pii, "probe: probe sendto"); } /* * If this is a PROBE_UNI probe packet being unicast to a target, then * update our tables. We will need this info in processing the probe * response. PROBE_MULTI and PROBE_RTT packets are not used for * the purpose of failure or recovery detection. PROBE_MULTI packets * are only used to construct a list of targets. PROBE_RTT packets are * used only for updating the rtt and not for failure detection. */ if (probe_type == PROBE_UNI && sent) { pr_ndx = pii->pii_probe_next; assert(pr_ndx >= 0 && pr_ndx < PROBE_STATS_COUNT); /* Collect statistics, before we reuse the last slot. */ if (pii->pii_probes[pr_ndx].pr_status == PR_LOST) pii->pii_cum_stats.lost++; else if (pii->pii_probes[pr_ndx].pr_status == PR_ACKED) pii->pii_cum_stats.acked++; pii->pii_cum_stats.sent++; pii->pii_probes[pr_ndx].pr_id = pii->pii_snxt; pii->pii_probes[pr_ndx].pr_tv_sent = sent_tv; pii->pii_probes[pr_ndx].pr_hrtime_sent = sent_hrtime; pii->pii_probes[pr_ndx].pr_hrtime_start = start_hrtime; pii->pii_probes[pr_ndx].pr_target = pii->pii_target_next; probe_chstate(&pii->pii_probes[pr_ndx], pii, PR_UNACKED); pii->pii_probe_next = PROBE_INDEX_NEXT(pii->pii_probe_next); pii->pii_target_next = target_next(pii->pii_target_next); assert(pii->pii_target_next != NULL); /* * If we have a single variable to denote the next target to * probe for both rtt probes and failure detection probes, we * could end up with a situation where the failure detection * probe targets become disjoint from the rtt probe targets. * Eg. if 2 targets and the actual fdt is double the user * specified fdt. So we have 2 variables. In this scheme * we also reset pii_rtt_target_next for every fdt probe, * though that may not be necessary. */ pii->pii_rtt_target_next = pii->pii_target_next; pii->pii_snxt++; } else if (probe_type == PROBE_RTT) { pii->pii_rtt_target_next = target_next(pii->pii_rtt_target_next); assert(pii->pii_rtt_target_next != NULL); } } /* * Incoming IPv4 data from wire, is received here. Called from main. */ void in_data(struct phyint_instance *pii) { struct sockaddr_in from; struct in6_addr fromaddr; static uint64_t in_packet[(IP_MAXPACKET + 1)/8]; static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8]; struct ip *ip; int iphlen; int len; char abuf[INET_ADDRSTRLEN]; struct msghdr msg; struct iovec iov; struct pr_icmp *reply; struct timeval *recv_tvp; if (debug & D_PROBE) { logdebug("in_data(%s %s)\n", AF_STR(pii->pii_af), pii->pii_name); } iov.iov_base = (char *)in_packet; iov.iov_len = sizeof (in_packet); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = (struct sockaddr *)&from; msg.msg_namelen = sizeof (from); msg.msg_control = ancillary_data; msg.msg_controllen = sizeof (ancillary_data); /* * Poll has already told us that a message is waiting, * on this socket. Read it now. We should not block. */ if ((len = recvmsg(pii->pii_probe_sock, &msg, 0)) < 0) { logperror_pii(pii, "in_data: recvmsg"); return; } /* * If the datalink has indicated the link is down, don't go * any further. */ if (LINK_DOWN(pii->pii_phyint)) return; /* Get the printable address for error reporting */ (void) inet_ntop(AF_INET, &from.sin_addr, abuf, sizeof (abuf)); /* Ignore packets > 64k or control buffers that don't fit */ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { if (debug & D_PKTBAD) { logdebug("Truncated message: msg_flags 0x%x from %s\n", msg.msg_flags, abuf); } return; } /* Make sure packet contains at least minimum ICMP header */ ip = (struct ip *)in_packet; iphlen = ip->ip_hl << 2; if (len < iphlen + ICMP_MINLEN) { if (debug & D_PKTBAD) { logdebug("in_data: packet too short (%d bytes)" " from %s\n", len, abuf); } return; } /* * Subtract the IP hdr length, 'len' will be length of the probe * reply, starting from the icmp hdr. */ len -= iphlen; /* LINTED */ reply = (struct pr_icmp *)((char *)in_packet + iphlen); /* Probe replies are icmp echo replies. Ignore anything else */ if (reply->pr_icmp_type != ICMP_ECHO_REPLY) return; /* * The icmp id should match what we sent, which is stored * in pi_icmpid. The icmp code for reply must be 0. * The reply content must be a struct pr_icmp */ if (reply->pr_icmp_id != pii->pii_icmpid) { /* Not in response to our probe */ return; } if (reply->pr_icmp_code != 0) { logtrace("probe reply code %d from %s on %s\n", reply->pr_icmp_code, abuf, pii->pii_name); return; } if (len < sizeof (struct pr_icmp)) { logtrace("probe reply too short: %d bytes from %s on %s\n", len, abuf, pii->pii_name); return; } recv_tvp = find_ancillary(&msg, SOL_SOCKET, SCM_TIMESTAMP); if (recv_tvp == NULL) { logtrace("message without timestamp from %s on %s\n", abuf, pii->pii_name); return; } IN6_INADDR_TO_V4MAPPED(&from.sin_addr, &fromaddr); if (reply->pr_icmp_mtype == htonl(PROBE_UNI)) /* Unicast probe reply */ incoming_echo_reply(pii, reply, fromaddr, recv_tvp); else if (reply->pr_icmp_mtype == htonl(PROBE_MULTI)) { /* Multicast reply */ incoming_mcast_reply(pii, reply, fromaddr); } else if (reply->pr_icmp_mtype == htonl(PROBE_RTT)) { incoming_rtt_reply(pii, reply, fromaddr); } else { /* Probably not in response to our probe */ logtrace("probe reply type: %d from %s on %s\n", reply->pr_icmp_mtype, abuf, pii->pii_name); return; } } /* * Incoming IPv6 data from wire is received here. Called from main. */ void in6_data(struct phyint_instance *pii) { struct sockaddr_in6 from; static uint64_t in_packet[(IP_MAXPACKET + 1)/8]; static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8]; int len; char abuf[INET6_ADDRSTRLEN]; struct msghdr msg; struct iovec iov; void *opt; struct pr_icmp *reply; struct timeval *recv_tvp; if (debug & D_PROBE) { logdebug("in6_data(%s %s)\n", AF_STR(pii->pii_af), pii->pii_name); } iov.iov_base = (char *)in_packet; iov.iov_len = sizeof (in_packet); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = (struct sockaddr *)&from; msg.msg_namelen = sizeof (from); msg.msg_control = ancillary_data; msg.msg_controllen = sizeof (ancillary_data); if ((len = recvmsg(pii->pii_probe_sock, &msg, 0)) < 0) { logperror_pii(pii, "in6_data: recvmsg"); return; } /* * If the datalink has indicated that the link is down, don't go * any further. */ if (LINK_DOWN(pii->pii_phyint)) return; /* Get the printable address for error reporting */ (void) inet_ntop(AF_INET6, &from.sin6_addr, abuf, sizeof (abuf)); if (len < ICMP_MINLEN) { if (debug & D_PKTBAD) { logdebug("Truncated message: msg_flags 0x%x from %s\n", msg.msg_flags, abuf); } return; } /* Ignore packets > 64k or control buffers that don't fit */ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { if (debug & D_PKTBAD) { logdebug("Truncated message: msg_flags 0x%x from %s\n", msg.msg_flags, abuf); } return; } reply = (struct pr_icmp *)in_packet; if (reply->pr_icmp_type != ICMP6_ECHO_REPLY) return; if (reply->pr_icmp_id != pii->pii_icmpid) { /* Not in response to our probe */ return; } /* * The kernel has already verified the the ICMP checksum. */ if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) { logtrace("ICMPv6 echo reply source address not linklocal from " "%s on %s\n", abuf, pii->pii_name); return; } opt = find_ancillary(&msg, IPPROTO_IPV6, IPV6_RTHDR); if (opt != NULL) { /* Can't allow routing headers in probe replies */ logtrace("message with routing header from %s on %s\n", abuf, pii->pii_name); return; } if (reply->pr_icmp_code != 0) { logtrace("probe reply code: %d from %s on %s\n", reply->pr_icmp_code, abuf, pii->pii_name); return; } if (len < (sizeof (struct pr_icmp))) { logtrace("probe reply too short: %d bytes from %s on %s\n", len, abuf, pii->pii_name); return; } recv_tvp = find_ancillary(&msg, SOL_SOCKET, SCM_TIMESTAMP); if (recv_tvp == NULL) { logtrace("message without timestamp from %s on %s\n", abuf, pii->pii_name); return; } if (reply->pr_icmp_mtype == htonl(PROBE_UNI)) { incoming_echo_reply(pii, reply, from.sin6_addr, recv_tvp); } else if (reply->pr_icmp_mtype == htonl(PROBE_MULTI)) { incoming_mcast_reply(pii, reply, from.sin6_addr); } else if (reply->pr_icmp_mtype == htonl(PROBE_RTT)) { incoming_rtt_reply(pii, reply, from.sin6_addr); } else { /* Probably not in response to our probe */ logtrace("probe reply type: %d from %s on %s\n", reply->pr_icmp_mtype, abuf, pii->pii_name); } } /* * Process the incoming rtt reply, in response to our rtt probe. * Common for both IPv4 and IPv6. Unlike incoming_echo_reply() we don't * have any stored information about the probe we sent. So we don't log * any errors if we receive bad replies. */ static void incoming_rtt_reply(struct phyint_instance *pii, struct pr_icmp *reply, struct in6_addr fromaddr) { int64_t m; /* rtt measurement in ns */ char abuf[INET6_ADDRSTRLEN]; struct target *target; struct phyint_group *pg; /* Get the printable address for error reporting */ (void) pr_addr(pii->pii_af, fromaddr, abuf, sizeof (abuf)); if (debug & D_PROBE) { logdebug("incoming_rtt_reply: %s %s %s\n", AF_STR(pii->pii_af), pii->pii_name, abuf); } /* Do we know this target ? */ target = target_lookup(pii, fromaddr); if (target == NULL) return; m = (int64_t)(gethrtime() - ntohll(reply->pr_icmp_timestamp)); /* Invalid rtt. It has wrapped around */ if (m < 0) return; /* * Don't update rtt until we see NUM_PROBE_REPAIRS probe responses * The initial few responses after the interface is repaired may * contain high rtt's because they could have been queued up waiting * for ARP/NDP resolution on a failed interface. */ pg = pii->pii_phyint->pi_group; if ((pii->pii_state != PI_RUNNING) || GROUP_FAILED(pg)) return; /* * Update rtt only if the new rtt is lower than the current rtt. * (specified by the 3rd parameter to pi_set_crtt). * If a spike has caused the current probe_interval to be > * user_probe_interval, then this mechanism is used to bring down * the rtt rapidly once the network stress is removed. * If the new rtt is higher than the current rtt, we don't want to * update the rtt. We are having more than 1 outstanding probe and * the increase in rtt we are seeing is being unnecessarily weighted * many times. The regular rtt update will be handled by * incoming_echo_reply() and will take care of any rtt increase. */ pi_set_crtt(target, m, _B_FALSE); if ((target->tg_crtt < (pg->pg_probeint / LOWER_FDT_TRIGGER)) && (user_failure_detection_time < pg->pg_fdt) && (last_fdt_bumpup_time + MIN_SETTLING_TIME < gethrtime())) { /* * If the crtt has now dropped by a factor of LOWER_FT_TRIGGER, * investigate if we can improve the failure detection time to * meet whatever the user specified. */ if (check_pg_crtt_improved(pg)) { pg->pg_fdt = MAX(pg->pg_fdt / NEXT_FDT_MULTIPLE, user_failure_detection_time); pg->pg_probeint = pg->pg_fdt / (NUM_PROBE_FAILS + 2); if (pii->pii_phyint->pi_group != phyint_anongroup) { logerr("Improved failure detection time %d ms " "on (%s %s) for group \"%s\"\n", pg->pg_fdt, AF_STR(pii->pii_af), pii->pii_name, pii->pii_phyint->pi_group->pg_name); } if (user_failure_detection_time == pg->pg_fdt) { /* Avoid any truncation or rounding errors */ pg->pg_probeint = user_probe_interval; /* * No more rtt probes will be sent. The actual * fdt has dropped to the user specified value. * pii_fd_snxt_basetime and pii_snxt_basetime * will be in sync henceforth. */ reset_snxt_basetimes(); } } } } /* * Process the incoming echo reply, in response to our unicast probe. * Common for both IPv4 and IPv6 */ static void incoming_echo_reply(struct phyint_instance *pii, struct pr_icmp *reply, struct in6_addr fromaddr, struct timeval *recv_tvp) { int64_t m; /* rtt measurement in ns */ hrtime_t cur_hrtime; /* in ns from some arbitrary point */ char abuf[INET6_ADDRSTRLEN]; int pr_ndx; struct target *target; boolean_t exception; uint64_t pr_icmp_timestamp; uint16_t pr_icmp_seq; struct probe_stats *pr_statp; struct phyint_group *pg = pii->pii_phyint->pi_group; /* Get the printable address for error reporting */ (void) pr_addr(pii->pii_af, fromaddr, abuf, sizeof (abuf)); if (debug & D_PROBE) { logdebug("incoming_echo_reply: %s %s %s seq %u recv_tvp %lld\n", AF_STR(pii->pii_af), pii->pii_name, abuf, ntohs(reply->pr_icmp_seq), tv2ns(recv_tvp)); } pr_icmp_timestamp = ntohll(reply->pr_icmp_timestamp); pr_icmp_seq = ntohs(reply->pr_icmp_seq); /* Reject out of window probe replies */ if (SEQ_GE(pr_icmp_seq, pii->pii_snxt) || SEQ_LT(pr_icmp_seq, pii->pii_snxt - PROBE_STATS_COUNT)) { logtrace("out of window probe seq %u snxt %u on %s from %s\n", pr_icmp_seq, pii->pii_snxt, pii->pii_name, abuf); pii->pii_cum_stats.unknown++; return; } cur_hrtime = gethrtime(); m = (int64_t)(cur_hrtime - pr_icmp_timestamp); if (m < 0) { /* * This is a ridiculously high value of rtt. rtt has wrapped * around. Log a message, and ignore the rtt. */ logerr("incoming_echo_reply: rtt wraparound cur_hrtime %lld " "reply timestamp %lld\n", cur_hrtime, pr_icmp_timestamp); } /* * Get the probe index pr_ndx corresponding to the received icmp seq. * number in our pii->pii_probes[] array. The icmp sequence number * pii_snxt corresponds to the probe index pii->pii_probe_next */ pr_ndx = MOD_SUB(pii->pii_probe_next, (uint16_t)(pii->pii_snxt - pr_icmp_seq), PROBE_STATS_COUNT); assert(PR_STATUS_VALID(pii->pii_probes[pr_ndx].pr_status)); target = pii->pii_probes[pr_ndx].pr_target; /* * Perform sanity checks, whether this probe reply that we * have received is genuine */ if (target != NULL) { /* * Compare the src. addr of the received ICMP or ICMPv6 * probe reply with the target address in our tables. */ if (!IN6_ARE_ADDR_EQUAL(&target->tg_address, &fromaddr)) { /* * We don't have any record of having sent a probe to * this target. This is a fake probe reply. Log an error */ logtrace("probe status %d Fake probe reply seq %u " "snxt %u on %s from %s\n", pii->pii_probes[pr_ndx].pr_status, pr_icmp_seq, pii->pii_snxt, pii->pii_name, abuf); pii->pii_cum_stats.unknown++; return; } else if (pii->pii_probes[pr_ndx].pr_status == PR_ACKED) { /* * The address matches, but our tables indicate that * this probe reply has been acked already. So this * is a duplicate probe reply. Log an error */ logtrace("probe status %d Duplicate probe reply seq %u " "snxt %u on %s from %s\n", pii->pii_probes[pr_ndx].pr_status, pr_icmp_seq, pii->pii_snxt, pii->pii_name, abuf); pii->pii_cum_stats.unknown++; return; } } else { /* * Target must not be NULL in the PR_UNACKED state */ assert(pii->pii_probes[pr_ndx].pr_status != PR_UNACKED); if (pii->pii_probes[pr_ndx].pr_status == PR_UNUSED) { /* * The probe stats slot is unused. So we didn't * send out any probe to this target. This is a fake. * Log an error. */ logtrace("probe status %d Fake probe reply seq %u " "snxt %u on %s from %s\n", pii->pii_probes[pr_ndx].pr_status, pr_icmp_seq, pii->pii_snxt, pii->pii_name, abuf); } pii->pii_cum_stats.unknown++; return; } /* * If the rtt does not appear to be right, don't update the * rtt stats. This can happen if the system dropped into the * debugger, or the system was hung or too busy for a * substantial time that we didn't get a chance to run. */ if ((m < 0) || (ns2ms(m) > PROBE_STATS_COUNT * pg->pg_probeint)) { /* * If the probe corresponding to this received response * was truly sent 'm' ns. ago, then this response must * have been rejected by the sequence number checks. The * fact that it has passed the sequence number checks * means that the measured rtt is wrong. We were probably * scheduled long after the packet was received. */ goto out; } /* * Don't update rtt until we see NUM_PROBE_REPAIRS probe responses * The initial few responses after the interface is repaired may * contain high rtt's because they could have been queued up waiting * for ARP/NDP resolution on a failed interface. */ if ((pii->pii_state != PI_RUNNING) || GROUP_FAILED(pg)) goto out; /* * Don't update the Conservative Round Trip Time estimate for this * (phint, target) pair if this is the not the highest ack seq seen * thus far on this target. */ if (!highest_ack_tg(pr_icmp_seq, target)) goto out; /* * Always update the rtt. This is a failure detection probe * and we want to measure both increase / decrease in rtt. */ pi_set_crtt(target, m, _B_TRUE); /* * If the crtt exceeds the average time between probes, * investigate if this slow target is an exception. If so we * can avoid this target and still meet the failure detection * time. Otherwise we can't meet the failure detection time. */ if (target->tg_crtt > pg->pg_probeint) { exception = check_exception_target(pii, target); if (exception) { /* * This target is exceptionally slow. Don't use it * for future probes. check_exception_target() has * made sure that we have at least MIN_PROBE_TARGETS * other active targets */ if (pii->pii_targets_are_routers) { /* * This is a slow router, mark it as slow * and don't use it for further probes. We * don't delete it, since it will be populated * again when we do a router scan. Hence we * need to maintain extra state (unlike the * host case below). Mark it as TG_SLOW. */ if (target->tg_status == TG_ACTIVE) pii->pii_ntargets--; target->tg_status = TG_SLOW; target->tg_latime = gethrtime(); target->tg_rtt_sa = -1; target->tg_crtt = 0; target->tg_rtt_sd = 0; if (pii->pii_target_next == target) { pii->pii_target_next = target_next(target); } } else { /* * the slow target is not a router, we can * just delete it. Send an icmp multicast and * pick the fastest responder that is not * already an active target. target_delete() * adjusts pii->pii_target_next */ target_delete(target); probe(pii, PROBE_MULTI, cur_hrtime); } } else { /* * We can't meet the failure detection time. * Log a message, and update the detection time to * whatever we can achieve. */ pg->pg_probeint = target->tg_crtt * NEXT_FDT_MULTIPLE; pg->pg_fdt = pg->pg_probeint * (NUM_PROBE_FAILS + 2); last_fdt_bumpup_time = gethrtime(); if (pg != phyint_anongroup) { logtrace("Cannot meet requested failure" " detection time of %d ms on (%s %s) new" " failure detection time for group \"%s\"" " is %d ms\n", user_failure_detection_time, AF_STR(pii->pii_af), pii->pii_name, pg->pg_name, pg->pg_fdt); } } } else if ((target->tg_crtt < (pg->pg_probeint / LOWER_FDT_TRIGGER)) && (user_failure_detection_time < pg->pg_fdt) && (last_fdt_bumpup_time + MIN_SETTLING_TIME < gethrtime())) { /* * If the crtt has now dropped by a factor of LOWER_FDT_TRIGGER * investigate if we can improve the failure detection time to * meet whatever the user specified. */ if (check_pg_crtt_improved(pg)) { pg->pg_fdt = MAX(pg->pg_fdt / NEXT_FDT_MULTIPLE, user_failure_detection_time); pg->pg_probeint = pg->pg_fdt / (NUM_PROBE_FAILS + 2); if (pg != phyint_anongroup) { logtrace("Improved failure detection time %d ms" " on (%s %s) for group \"%s\"\n", pg->pg_fdt, AF_STR(pii->pii_af), pii->pii_name, pg->pg_name); } if (user_failure_detection_time == pg->pg_fdt) { /* Avoid any truncation or rounding errors */ pg->pg_probeint = user_probe_interval; /* * No more rtt probes will be sent. The actual * fdt has dropped to the user specified value. * pii_fd_snxt_basetime and pii_snxt_basetime * will be in sync henceforth. */ reset_snxt_basetimes(); } } } out: pr_statp = &pii->pii_probes[pr_ndx]; pr_statp->pr_hrtime_ackproc = cur_hrtime; pr_statp->pr_hrtime_ackrecv = pr_statp->pr_hrtime_sent + (tv2ns(recv_tvp) - tv2ns(&pr_statp->pr_tv_sent)); probe_chstate(pr_statp, pii, PR_ACKED); /* * Update pii->pii_rack, i.e. the sequence number of the last received * probe response, based on the echo reply we have received now, if * either of the following conditions are satisfied. * a. pii_rack is outside the current receive window of * [pii->pii_snxt - PROBE_STATS_COUNT, pii->pii_snxt). * This means we have not received probe responses for a * long time, and the sequence number has wrapped around. * b. pii_rack is within the current receive window and this echo * reply corresponds to the highest sequence number we have seen * so far. */ if (SEQ_GE(pii->pii_rack, pii->pii_snxt) || SEQ_LT(pii->pii_rack, pii->pii_snxt - PROBE_STATS_COUNT) || SEQ_GT(pr_icmp_seq, pii->pii_rack)) { pii->pii_rack = pr_icmp_seq; } } /* * Returns true if seq is the highest unacknowledged seq for target tg * else returns false */ static boolean_t highest_ack_tg(uint16_t seq, struct target *tg) { struct phyint_instance *pii; int pr_ndx; uint16_t pr_seq; pii = tg->tg_phyint_inst; /* * Get the seq number of the most recent probe sent so far, * and also get the corresponding probe index in the probe stats * array. */ pr_ndx = PROBE_INDEX_PREV(pii->pii_probe_next); pr_seq = pii->pii_snxt; pr_seq--; /* * Start from the most recent probe and walk back, trying to find * an acked probe corresponding to target tg. */ for (; pr_ndx != pii->pii_probe_next; pr_ndx = PROBE_INDEX_PREV(pr_ndx), pr_seq--) { if (pii->pii_probes[pr_ndx].pr_target == tg && pii->pii_probes[pr_ndx].pr_status == PR_ACKED) { if (SEQ_GT(pr_seq, seq)) return (_B_FALSE); } } return (_B_TRUE); } /* * Check whether the crtt for the group has improved by a factor of * LOWER_FDT_TRIGGER. Small crtt improvements are ignored to avoid failure * detection time flapping in the face of small crtt changes. */ static boolean_t check_pg_crtt_improved(struct phyint_group *pg) { struct phyint *pi; if (debug & D_PROBE) logdebug("check_pg_crtt_improved()\n"); /* * The crtt for the group is only improved if each phyint_instance * for both ipv4 and ipv6 is improved. */ for (pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext) { if (!check_pii_crtt_improved(pi->pi_v4) || !check_pii_crtt_improved(pi->pi_v6)) return (_B_FALSE); } return (_B_TRUE); } /* * Check whether the crtt has improved substantially on this phyint_instance. * Returns _B_TRUE if there's no crtt information available, because pii * is NULL or the phyint_instance is not capable of probing. */ boolean_t check_pii_crtt_improved(struct phyint_instance *pii) { struct target *tg; if (pii == NULL) return (_B_TRUE); if (!PROBE_CAPABLE(pii) || pii->pii_phyint->pi_state == PI_FAILED) return (_B_TRUE); for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { if (tg->tg_status != TG_ACTIVE) continue; if (tg->tg_crtt > (pii->pii_phyint->pi_group->pg_probeint / LOWER_FDT_TRIGGER)) { return (_B_FALSE); } } return (_B_TRUE); } /* * This target responds very slowly to probes. The target's crtt exceeds * the probe interval of its group. Compare against other targets * and determine if this target is an exception, if so return true, else false */ static boolean_t check_exception_target(struct phyint_instance *pii, struct target *target) { struct target *tg; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PROBE) { logdebug("check_exception_target(%s %s target %s)\n", AF_STR(pii->pii_af), pii->pii_name, pr_addr(pii->pii_af, target->tg_address, abuf, sizeof (abuf))); } /* * We should have at least MIN_PROBE_TARGETS + 1 good targets now, * to make a good judgement. Otherwise don't drop this target. */ if (pii->pii_ntargets < MIN_PROBE_TARGETS + 1) return (_B_FALSE); /* * Determine whether only this particular target is slow. * We know that this target's crtt exceeds the group's probe interval. * If all other active targets have a * crtt < (this group's probe interval) / EXCEPTION_FACTOR, * then this target is considered slow. */ for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { if (tg != target && tg->tg_status == TG_ACTIVE) { if (tg->tg_crtt > pii->pii_phyint->pi_group->pg_probeint / EXCEPTION_FACTOR) { return (_B_FALSE); } } } return (_B_TRUE); } /* * Update the target list. The icmp all hosts multicast has given us * some host to which we can send probes. If we already have sufficient * targets, discard it. */ static void incoming_mcast_reply(struct phyint_instance *pii, struct pr_icmp *reply, struct in6_addr fromaddr) /* ARGSUSED */ { int af; char abuf[INET6_ADDRSTRLEN]; struct phyint *pi; if (debug & D_PROBE) { logdebug("incoming_mcast_reply(%s %s %s)\n", AF_STR(pii->pii_af), pii->pii_name, pr_addr(pii->pii_af, fromaddr, abuf, sizeof (abuf))); } /* * Using host targets is a fallback mechanism. If we have * found a router, don't add this host target. If we already * know MAX_PROBE_TARGETS, don't add another target. */ assert(pii->pii_ntargets <= MAX_PROBE_TARGETS); if (pii->pii_targets != NULL) { if (pii->pii_targets_are_routers || (pii->pii_ntargets == MAX_PROBE_TARGETS)) { return; } } if (IN6_IS_ADDR_UNSPECIFIED(&fromaddr) || IN6_IS_ADDR_V4MAPPED_ANY(&fromaddr)) { /* * Guard against response from 0.0.0.0 * and ::. Log a trace message */ logtrace("probe response from %s on %s\n", pr_addr(pii->pii_af, fromaddr, abuf, sizeof (abuf)), pii->pii_name); return; } /* * This address is one of our own, so reject this address as a * valid probe target. */ af = pii->pii_af; if (own_address(fromaddr)) return; /* * If the phyint is part a named group, then add the address to all * members of the group. Otherwise, add the address only to the * phyint itself, since other phyints in the anongroup may not be on * the same subnet. */ pi = pii->pii_phyint; if (pi->pi_group == phyint_anongroup) { target_add(pii, fromaddr, _B_FALSE); } else { pi = pi->pi_group->pg_phyint; for (; pi != NULL; pi = pi->pi_pgnext) target_add(PHYINT_INSTANCE(pi, af), fromaddr, _B_FALSE); } } /* * Compute CRTT given an existing scaled average, scaled deviation estimate * and a new rtt time. The formula is from Jacobson and Karels' * "Congestion Avoidance and Control" in SIGCOMM '88. The variable names * are the same as those in Appendix A.2 of that paper. * * m = new measurement * sa = scaled RTT average (8 * average estimates) * sv = scaled mean deviation (mdev) of RTT (4 * deviation estimates). * crtt = Conservative round trip time. Used to determine whether probe * has timed out. * * New scaled average and deviation are passed back via sap and svp */ static int64_t compute_crtt(int64_t *sap, int64_t *svp, int64_t m) { int64_t sa = *sap; int64_t sv = *svp; int64_t crtt; int64_t saved_m = m; assert(*sap >= -1); assert(*svp >= 0); if (sa != -1) { /* * Update average estimator: * new rtt = old rtt + 1/8 Error * where Error = m - old rtt * i.e. 8 * new rtt = 8 * old rtt + Error * i.e. new sa = old sa + Error */ m -= sa >> 3; /* m is now Error in estimate. */ if ((sa += m) < 0) { /* Don't allow the smoothed average to be negative. */ sa = 0; } /* * Update deviation estimator: * new mdev = old mdev + 1/4 (abs(Error) - old mdev) * i.e. 4 * new mdev = 4 * old mdev + * (abs(Error) - old mdev) * i.e. new sv = old sv + (abs(Error) - old mdev) */ if (m < 0) m = -m; m -= sv >> 2; sv += m; } else { /* Initialization. This is the first response received. */ sa = (m << 3); sv = (m << 1); } crtt = (sa >> 3) + sv; if (debug & D_PROBE) { logerr("compute_crtt: m = %lld sa = %lld, sv = %lld -> " "crtt = %lld\n", saved_m, sa, sv, crtt); } *sap = sa; *svp = sv; /* * CRTT = average estimates + 4 * deviation estimates * = sa / 8 + sv */ return (crtt); } static void pi_set_crtt(struct target *tg, int64_t m, boolean_t is_probe_uni) { struct phyint_instance *pii = tg->tg_phyint_inst; int probe_interval = pii->pii_phyint->pi_group->pg_probeint; int64_t sa = tg->tg_rtt_sa; int64_t sv = tg->tg_rtt_sd; int new_crtt; int i; if (debug & D_PROBE) logdebug("pi_set_crtt: target - m %lld\n", m); /* store the round trip time, in case we need to defer computation */ tg->tg_deferred[tg->tg_num_deferred] = m; new_crtt = ns2ms(compute_crtt(&sa, &sv, m)); /* * If this probe's round trip time would singlehandedly cause an * increase in the group's probe interval consider it suspect. */ if ((new_crtt > probe_interval) && is_probe_uni) { if (debug & D_PROBE) { logdebug("Received a suspect probe on %s, new_crtt =" " %d, probe_interval = %d, num_deferred = %d\n", pii->pii_probe_logint->li_name, new_crtt, probe_interval, tg->tg_num_deferred); } /* * If we've deferred as many rtts as we plan on deferring, then * assume the link really did slow down and process all queued * rtts */ if (tg->tg_num_deferred == MAXDEFERREDRTT) { if (debug & D_PROBE) { logdebug("Received MAXDEFERREDRTT probes which " "would cause an increased probe_interval. " "Integrating queued rtt data points.\n"); } for (i = 0; i <= tg->tg_num_deferred; i++) { tg->tg_crtt = ns2ms(compute_crtt(&tg->tg_rtt_sa, &tg->tg_rtt_sd, tg->tg_deferred[i])); } tg->tg_num_deferred = 0; } else { tg->tg_num_deferred++; } return; } /* * If this is a normal probe, or an RTT probe that would lead to a * reduced CRTT, then update our CRTT data. Further, if this was * a normal probe, pitch any deferred probes since our probes are * again being answered within our CRTT estimates. */ if (is_probe_uni || new_crtt < tg->tg_crtt) { tg->tg_rtt_sa = sa; tg->tg_rtt_sd = sv; tg->tg_crtt = new_crtt; if (is_probe_uni) tg->tg_num_deferred = 0; } } /* * Return a pointer to the specified option buffer. * If not found return NULL. */ static void * find_ancillary(struct msghdr *msg, int cmsg_level, int cmsg_type) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == cmsg_level && cmsg->cmsg_type == cmsg_type) { return (CMSG_DATA(cmsg)); } } return (NULL); } /* * Try to activate another INACTIVE interface in the same group as `pi'. * Prefer STANDBY INACTIVE to just INACTIVE. */ void phyint_activate_another(struct phyint *pi) { struct phyint *pi2; struct phyint *inactivepi = NULL; if (pi->pi_group == phyint_anongroup) return; for (pi2 = pi->pi_group->pg_phyint; pi2 != NULL; pi2 = pi2->pi_pgnext) { if (pi == pi2 || !phyint_is_functioning(pi2) || !(pi2->pi_flags & IFF_INACTIVE)) continue; inactivepi = pi2; if (pi2->pi_flags & IFF_STANDBY) break; } if (inactivepi != NULL) (void) change_pif_flags(inactivepi, 0, IFF_INACTIVE); } /* * Transition a phyint to PI_RUNNING. The caller must ensure that the * transition is appropriate. Clears IFF_OFFLINE or IFF_FAILED if * appropriate. Also sets IFF_INACTIVE on this or other interfaces as * appropriate (see comment below). Finally, also updates the phyint's group * state to account for the change. */ void phyint_transition_to_running(struct phyint *pi) { struct phyint *pi2; struct phyint *actstandbypi = NULL; uint_t nactive = 0, nnonstandby = 0; boolean_t onlining = (pi->pi_state == PI_OFFLINE); boolean_t initial = (pi->pi_state == PI_INIT); uint64_t set, clear; /* * The interface is running again, but should it or another interface * in the group end up INACTIVE? There are three cases: * * 1. If it's a STANDBY interface, it should be end up INACTIVE if * the group is operating at capacity (i.e., there are at least as * many active interfaces as non-STANDBY interfaces in the group). * No other interfaces should be changed. * * 2. If it's a non-STANDBY interface and we're onlining it or * FAILBACK is enabled, then it should *not* end up INACTIVE. * Further, if the group is above capacity as a result of this * interface, then an active STANDBY interface in the group should * end up INACTIVE. * * 3. If it's a non-STANDBY interface, we're repairing it, and * FAILBACK is disabled, then it should end up INACTIVE *unless* * the group was failed (in which case we have no choice but to * use it). No other interfaces should be changed. */ if (pi->pi_group != phyint_anongroup) { pi2 = pi->pi_group->pg_phyint; for (; pi2 != NULL; pi2 = pi2->pi_pgnext) { if (!(pi2->pi_flags & IFF_STANDBY)) nnonstandby++; if (phyint_is_functioning(pi2) && !(pi2->pi_flags & IFF_INACTIVE)) { nactive++; if (pi2->pi_flags & IFF_STANDBY) actstandbypi = pi2; } } } set = 0; clear = (onlining ? IFF_OFFLINE : IFF_FAILED); if (pi->pi_flags & IFF_STANDBY) { /* case 1 */ if (nactive >= nnonstandby) set |= IFF_INACTIVE; else clear |= IFF_INACTIVE; } else if (onlining || failback_enabled) { /* case 2 */ if (nactive >= nnonstandby && actstandbypi != NULL) (void) change_pif_flags(actstandbypi, IFF_INACTIVE, 0); } else if (!initial && !GROUP_FAILED(pi->pi_group)) { /* case 3 */ set |= IFF_INACTIVE; } (void) change_pif_flags(pi, set, clear); phyint_chstate(pi, PI_RUNNING); /* * Update the group state to account for the change. */ phyint_group_refresh_state(pi->pi_group); } /* * Adjust IFF_INACTIVE on the provided `pi' to trend the group configuration * to have at least one active interface and as many active interfaces as * non-standby interfaces. */ void phyint_standby_refresh_inactive(struct phyint *pi) { struct phyint *pi2; uint_t nactive = 0, nnonstandby = 0; /* * All phyints in the anonymous group are effectively in their own * group and thus active regardless of whether they're marked standby. */ if (pi->pi_group == phyint_anongroup) { (void) change_pif_flags(pi, 0, IFF_INACTIVE); return; } /* * If the phyint isn't functioning we can't consider it. */ if (!phyint_is_functioning(pi)) return; for (pi2 = pi->pi_group->pg_phyint; pi2 != NULL; pi2 = pi2->pi_pgnext) { if (!(pi2->pi_flags & IFF_STANDBY)) nnonstandby++; if (phyint_is_functioning(pi2) && !(pi2->pi_flags & IFF_INACTIVE)) nactive++; } if (nactive == 0 || nactive < nnonstandby) (void) change_pif_flags(pi, 0, IFF_INACTIVE); else if (nactive > nnonstandby) (void) change_pif_flags(pi, IFF_INACTIVE, 0); } /* * See if a previously failed interface has started working again. */ void phyint_check_for_repair(struct phyint *pi) { if (!phyint_repaired(pi)) return; if (pi->pi_group == phyint_anongroup) { logerr("IP interface repair detected on %s\n", pi->pi_name); } else { logerr("IP interface repair detected on %s of group %s\n", pi->pi_name, pi->pi_group->pg_name); } /* * If the interface is PI_OFFLINE, it can't be made PI_RUNNING yet. * So just clear IFF_OFFLINE and defer phyint_transition_to_running() * until it is brought back online. */ if (pi->pi_state == PI_OFFLINE) { (void) change_pif_flags(pi, 0, IFF_FAILED); return; } phyint_transition_to_running(pi); /* calls phyint_chstate() */ } /* * See if an interface has failed, or if the whole group of interfaces has * failed. */ static void phyint_inst_check_for_failure(struct phyint_instance *pii) { struct phyint *pi = pii->pii_phyint; struct phyint *pi2; boolean_t was_active; switch (failure_state(pii)) { case PHYINT_FAILURE: was_active = ((pi->pi_flags & IFF_INACTIVE) == 0); (void) change_pif_flags(pi, IFF_FAILED, IFF_INACTIVE); if (pi->pi_group == phyint_anongroup) { logerr("IP interface failure detected on %s\n", pii->pii_name); } else { logerr("IP interface failure detected on %s of group" " %s\n", pii->pii_name, pi->pi_group->pg_name); } /* * If the failed interface was active, activate another * INACTIVE interface in the group if possible. */ if (was_active) phyint_activate_another(pi); /* * If the interface is offline, the state change will be * noted when it comes back online. */ if (pi->pi_state != PI_OFFLINE) { phyint_chstate(pi, PI_FAILED); reset_crtt_all(pi); } break; case GROUP_FAILURE: pi2 = pi->pi_group->pg_phyint; for (; pi2 != NULL; pi2 = pi2->pi_pgnext) { (void) change_pif_flags(pi2, IFF_FAILED, IFF_INACTIVE); if (pi2->pi_state == PI_OFFLINE) /* see comment above */ continue; reset_crtt_all(pi2); /* * In the case of host targets, we would have flushed * the targets, and gone to PI_NOTARGETS state. */ if (pi2->pi_state == PI_RUNNING) phyint_chstate(pi2, PI_FAILED); } break; default: break; } } /* * Determines if any timeout event has occurred and returns the number of * milliseconds until the next timeout event for the phyint. Returns * TIMER_INFINITY for "never". */ uint_t phyint_inst_timer(struct phyint_instance *pii) { int pr_ndx; uint_t timeout; struct target *cur_tg; struct probe_stats *pr_statp; struct phyint_instance *pii_other; struct phyint *pi; int valid_unack_count; int i; int interval; uint_t check_time; uint_t cur_time; hrtime_t cur_hrtime; int probe_interval = pii->pii_phyint->pi_group->pg_probeint; cur_hrtime = gethrtime(); cur_time = ns2ms(cur_hrtime); if (debug & D_TIMER) { logdebug("phyint_inst_timer(%s %s)\n", AF_STR(pii->pii_af), pii->pii_name); } pii_other = phyint_inst_other(pii); if (!PROBE_ENABLED(pii) && !PROBE_ENABLED(pii_other)) { /* * Check to see if we're here due to link up/down flapping; If * enough time has passed, then try to bring the interface * back up; otherwise, schedule a timer to bring it back up * when enough time *has* elapsed. */ pi = pii->pii_phyint; if (pi->pi_state == PI_FAILED && LINK_UP(pi)) { check_time = pi->pi_whenup[pi->pi_whendx] + MSEC_PERMIN; if (check_time > cur_time) return (check_time - cur_time); phyint_check_for_repair(pi); } } /* * If probing is not enabled on this phyint instance, don't proceed. */ if (!PROBE_ENABLED(pii)) return (TIMER_INFINITY); /* * If the timer has fired too soon, probably triggered * by some other phyint instance, return the remaining * time */ if (TIME_LT(cur_time, pii->pii_snxt_time)) return (pii->pii_snxt_time - cur_time); /* * If the link is down, don't send any probes for now. */ if (LINK_DOWN(pii->pii_phyint)) return (TIMER_INFINITY); /* * Randomize the next probe time, between MIN_RANDOM_FACTOR * and MAX_RANDOM_FACTOR with respect to the base probe time. * Base probe time is strictly periodic. */ interval = GET_RANDOM( (int)(MIN_RANDOM_FACTOR * user_probe_interval), (int)(MAX_RANDOM_FACTOR * user_probe_interval)); pii->pii_snxt_time = pii->pii_snxt_basetime + interval; /* * Check if the current time > next time to probe. If so, we missed * sending 1 or more probes, probably due to heavy system load. At least * 'MIN_RANDOM_FACTOR * user_probe_interval' ms has elapsed since we * were scheduled. Make adjustments to the times, in multiples of * user_probe_interval. */ if (TIME_GT(cur_time, pii->pii_snxt_time)) { int n; n = (cur_time - pii->pii_snxt_time) / user_probe_interval; pii->pii_snxt_time += (n + 1) * user_probe_interval; pii->pii_snxt_basetime += (n + 1) * user_probe_interval; logtrace("missed sending %d probes cur_time %u snxt_time %u" " snxt_basetime %u\n", n + 1, cur_time, pii->pii_snxt_time, pii->pii_snxt_basetime); /* Collect statistics about missed probes */ probes_missed.pm_nprobes += n + 1; probes_missed.pm_ntimes++; } pii->pii_snxt_basetime += user_probe_interval; interval = pii->pii_snxt_time - cur_time; if (debug & D_TARGET) { logdebug("cur_time %u snxt_time %u snxt_basetime %u" " interval %u\n", cur_time, pii->pii_snxt_time, pii->pii_snxt_basetime, interval); } /* * If no targets are known, we need to send an ICMP multicast. The * probe type is PROBE_MULTI. We'll check back in 'interval' msec * to see if we found a target. */ if (pii->pii_target_next == NULL) { assert(pii->pii_ntargets == 0); pii->pii_fd_snxt_basetime = pii->pii_snxt_basetime; probe(pii, PROBE_MULTI, cur_time); return (interval); } if ((user_probe_interval != probe_interval) && TIME_LT(pii->pii_snxt_time, pii->pii_fd_snxt_basetime)) { /* * the failure detection (fd) probe timer has not yet fired. * Need to send only an rtt probe. The probe type is PROBE_RTT. */ probe(pii, PROBE_RTT, cur_hrtime); return (interval); } /* * the fd probe timer has fired. Need to do all failure * detection / recovery calculations, and then send an fd probe * of type PROBE_UNI. */ if (user_probe_interval == probe_interval) { /* * We could have missed some probes, and then adjusted * pii_snxt_basetime above. Otherwise we could have * blindly added probe_interval to pii_fd_snxt_basetime. */ pii->pii_fd_snxt_basetime = pii->pii_snxt_basetime; } else { pii->pii_fd_snxt_basetime += probe_interval; if (TIME_GT(cur_time, pii->pii_fd_snxt_basetime)) { int n; n = (cur_time - pii->pii_fd_snxt_basetime) / probe_interval; pii->pii_fd_snxt_basetime += (n + 1) * probe_interval; } } /* * We can have at most, the latest 2 probes that we sent, in * the PR_UNACKED state. All previous probes sent, are either * PR_LOST or PR_ACKED. An unacknowledged probe is considered * timed out if the probe's time_start + the CRTT < currenttime. * For each of the last 2 probes, examine whether it has timed * out. If so, mark it PR_LOST. The probe stats is a circular array. */ pr_ndx = PROBE_INDEX_PREV(pii->pii_probe_next); valid_unack_count = 0; for (i = 0; i < 2; i++) { pr_statp = &pii->pii_probes[pr_ndx]; cur_tg = pii->pii_probes[pr_ndx].pr_target; switch (pr_statp->pr_status) { case PR_ACKED: /* * We received back an ACK, so the switch clearly * is not dropping our traffic, and thus we can * enable failure detection immediately. */ if (pii->pii_fd_hrtime > gethrtime()) { if (debug & D_PROBE) { logdebug("successful probe on %s; " "ending quiet period\n", pii->pii_phyint->pi_name); } pii->pii_fd_hrtime = gethrtime(); } break; case PR_UNACKED: assert(cur_tg != NULL); /* * The crtt could be zero for some reason, * Eg. the phyint could be failed. If the crtt is * not available use group's probe interval, * which is a worst case estimate. */ timeout = ns2ms(pr_statp->pr_hrtime_start); if (cur_tg->tg_crtt != 0) { timeout += cur_tg->tg_crtt; } else { timeout += probe_interval; } if (TIME_LT(timeout, cur_time)) { pr_statp->pr_time_lost = timeout; probe_chstate(pr_statp, pii, PR_LOST); } else if (i == 1) { /* * We are forced to consider this probe * lost, as we can have at most 2 unack. * probes any time, and we will be sending a * probe at the end of this function. * Normally, we should not be here, but * this can happen if an incoming response * that was considered lost has increased * the crtt for this target, and also bumped * up the FDT. Note that we never cancel or * increase the current pii_time_left, so * when the timer fires, we find 2 valid * unacked probes, and they are yet to timeout */ pr_statp->pr_time_lost = cur_time; probe_chstate(pr_statp, pii, PR_LOST); } else { /* * Only the most recent probe can enter * this 'else' arm. The second most recent * probe must take either of the above arms, * if it is unacked. */ valid_unack_count++; } break; } pr_ndx = PROBE_INDEX_PREV(pr_ndx); } /* * We send out 1 probe randomly in the interval between one half * and one probe interval for the group. Given that the CRTT is always * less than the group's probe interval, we can have at most 1 * unacknowledged probe now. All previous probes are either lost or * acked. */ assert(valid_unack_count == 0 || valid_unack_count == 1); /* * The timer has fired. Take appropriate action depending * on the current state of the phyint. * * PI_RUNNING state - Failure detection * PI_FAILED state - Repair detection */ switch (pii->pii_phyint->pi_state) { case PI_FAILED: /* * If the most recent probe (excluding unacked probes that * are yet to time out) has been acked, check whether the * phyint is now repaired. */ if (pii->pii_rack + valid_unack_count + 1 == pii->pii_snxt) { phyint_check_for_repair(pii->pii_phyint); } break; case PI_RUNNING: /* * It's possible our probes have been lost because of a * spanning-tree mandated quiet period on the switch. If so, * ignore the lost probes. */ if (pii->pii_fd_hrtime - cur_hrtime > 0) break; if (pii->pii_rack + valid_unack_count + 1 != pii->pii_snxt) { /* * We have 1 or more failed probes (excluding unacked * probes that are yet to time out). Determine if the * phyint has failed. */ phyint_inst_check_for_failure(pii); } break; default: logerr("phyint_inst_timer: invalid state %d\n", pii->pii_phyint->pi_state); abort(); } /* * Start the next probe. probe() will also set pii->pii_probe_time_left * to the group's probe interval. If phyint_failed -> target_flush_hosts * was called, the target list may be empty. */ if (pii->pii_target_next != NULL) { probe(pii, PROBE_UNI, cur_hrtime); /* * If we have just the one probe target, and we're not using * router targets, try to find another as we presently have * no resilience. */ if (!pii->pii_targets_are_routers && pii->pii_ntargets == 1) probe(pii, PROBE_MULTI, cur_hrtime); } else { probe(pii, PROBE_MULTI, cur_hrtime); } return (interval); } /* * Start the probe timer for an interface instance. */ void start_timer(struct phyint_instance *pii) { uint32_t interval; /* * Spread the base probe times (pi_snxt_basetime) across phyints * uniformly over the (curtime..curtime + the group's probe_interval). * pi_snxt_basetime is strictly periodic with a frequency of * the group's probe interval. The actual probe time pi_snxt_time * adds some randomness to pi_snxt_basetime and happens in probe(). * For the 1st probe on each phyint after the timer is started, * pi_snxt_time and pi_snxt_basetime are the same. */ interval = GET_RANDOM(0, (int)pii->pii_phyint->pi_group->pg_probeint); pii->pii_snxt_basetime = getcurrenttime() + interval; pii->pii_fd_snxt_basetime = pii->pii_snxt_basetime; pii->pii_snxt_time = pii->pii_snxt_basetime; timer_schedule(interval); } /* * Restart the probe timer on an interface instance. */ static void restart_timer(struct phyint_instance *pii) { /* * We don't need to restart the timer if it was never started in * the first place (pii->pii_basetime_inited not set), as the timer * won't have gone off yet. */ if (pii->pii_basetime_inited != 0) { if (debug & D_LINKNOTE) logdebug("restart timer: restarting timer on %s, " "address family %s\n", pii->pii_phyint->pi_name, AF_STR(pii->pii_af)); start_timer(pii); } } static void process_link_state_down(struct phyint *pi) { logerr("The link has gone down on %s\n", pi->pi_name); /* * Clear the probe statistics arrays, we don't want the repair * detection logic relying on probes that were successful prior * to the link going down. */ if (PROBE_CAPABLE(pi->pi_v4)) clear_pii_probe_stats(pi->pi_v4); if (PROBE_CAPABLE(pi->pi_v6)) clear_pii_probe_stats(pi->pi_v6); /* * Check for interface failure. Although we know the interface * has failed, we don't know if all the other interfaces in the * group have failed as well. */ if ((pi->pi_state == PI_RUNNING) || (pi->pi_state != PI_FAILED && !GROUP_FAILED(pi->pi_group))) { if (debug & D_LINKNOTE) { logdebug("process_link_state_down:" " checking for failure on %s\n", pi->pi_name); } if (pi->pi_v4 != NULL) phyint_inst_check_for_failure(pi->pi_v4); else if (pi->pi_v6 != NULL) phyint_inst_check_for_failure(pi->pi_v6); } } static void process_link_state_up(struct phyint *pi) { logerr("The link has come up on %s\n", pi->pi_name); /* * We stopped any running timers on each instance when the link * went down, so restart them. */ if (pi->pi_v4) restart_timer(pi->pi_v4); if (pi->pi_v6) restart_timer(pi->pi_v6); phyint_check_for_repair(pi); pi->pi_whenup[pi->pi_whendx++] = getcurrenttime(); if (pi->pi_whendx == LINK_UP_PERMIN) pi->pi_whendx = 0; } /* * Process any changes in link state passed up from the interfaces. */ void process_link_state_changes(void) { struct phyint *pi; /* Look for interfaces where the link state has just changed */ for (pi = phyints; pi != NULL; pi = pi->pi_next) { boolean_t old_link_state_up = LINK_UP(pi); /* * Except when the "phyint" structure is created, this is * the only place the link state is updated. This allows * this routine to detect changes in link state, rather * than just the current state. */ UPDATE_LINK_STATE(pi); if (LINK_DOWN(pi)) { /* * Has link just gone down? */ if (old_link_state_up) process_link_state_down(pi); } else { /* * Has link just gone back up? */ if (!old_link_state_up) process_link_state_up(pi); } } } void reset_crtt_all(struct phyint *pi) { struct phyint_instance *pii; struct target *tg; pii = pi->pi_v4; if (pii != NULL) { for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { tg->tg_crtt = 0; tg->tg_rtt_sa = -1; tg->tg_rtt_sd = 0; } } pii = pi->pi_v6; if (pii != NULL) { for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { tg->tg_crtt = 0; tg->tg_rtt_sa = -1; tg->tg_rtt_sd = 0; } } } /* * Check if the phyint has failed the last NUM_PROBE_FAILS consecutive * probes on both instances IPv4 and IPv6. * If the interface has failed, return the time of the first probe failure * in "tff". */ static int phyint_inst_probe_failure_state(struct phyint_instance *pii, uint_t *tff) { uint_t pi_tff; struct target *cur_tg; struct probe_fail_count pfinfo; struct phyint_instance *pii_other; int pr_ndx; /* * Get the number of consecutive failed probes on * this phyint across all targets. Also get the number * of consecutive failed probes on this target only */ pr_ndx = PROBE_INDEX_PREV(pii->pii_probe_next); cur_tg = pii->pii_probes[pr_ndx].pr_target; probe_fail_info(pii, cur_tg, &pfinfo); /* Get the time of first failure, for later use */ pi_tff = pfinfo.pf_tff; /* * If the current target has not responded to the * last NUM_PROBE_FAILS probes, and other targets are * responding delete this target. Dead gateway detection * will eventually remove this target (if router) from the * routing tables. If that does not occur, we may end * up adding this to our list again. */ if (pfinfo.pf_nfail < NUM_PROBE_FAILS && pfinfo.pf_nfail_tg >= NUM_PROBE_FAILS) { if (pii->pii_targets_are_routers) { if (cur_tg->tg_status == TG_ACTIVE) pii->pii_ntargets--; cur_tg->tg_status = TG_DEAD; cur_tg->tg_crtt = 0; cur_tg->tg_rtt_sa = -1; cur_tg->tg_rtt_sd = 0; if (pii->pii_target_next == cur_tg) pii->pii_target_next = target_next(cur_tg); } else { target_delete(cur_tg); probe(pii, PROBE_MULTI, gethrtime()); } return (PHYINT_OK); } /* * If the phyint has lost NUM_PROBE_FAILS or more * consecutive probes, on both IPv4 and IPv6 protocol * instances of the phyint, then trigger failure * detection, else return false */ if (pfinfo.pf_nfail < NUM_PROBE_FAILS) return (PHYINT_OK); pii_other = phyint_inst_other(pii); if (PROBE_CAPABLE(pii_other)) { probe_fail_info(pii_other, NULL, &pfinfo); if (pfinfo.pf_nfail >= NUM_PROBE_FAILS) { /* * We have NUM_PROBE_FAILS or more failures * on both IPv4 and IPv6. Get the earliest * time when failure was detected on this * phyint across IPv4 and IPv6. */ if (TIME_LT(pfinfo.pf_tff, pi_tff)) pi_tff = pfinfo.pf_tff; } else { /* * This instance has < NUM_PROBE_FAILS failure. * So return false */ return (PHYINT_OK); } } *tff = pi_tff; return (PHYINT_FAILURE); } /* * Check if the link has gone down on this phyint, or it has failed the * last NUM_PROBE_FAILS consecutive probes on both instances IPv4 and IPv6. * Also look at other phyints of this group, for group failures. */ int failure_state(struct phyint_instance *pii) { struct probe_success_count psinfo; uint_t pi2_tls; /* time last success */ uint_t pi_tff; /* time first fail */ struct phyint *pi2; struct phyint *pi; struct phyint_instance *pii2; struct phyint_group *pg; int retval; if (debug & D_FAILREP) logdebug("phyint_failed(%s)\n", pii->pii_name); pi = pii->pii_phyint; pg = pi->pi_group; if (LINK_UP(pi) && phyint_inst_probe_failure_state(pii, &pi_tff) == PHYINT_OK) return (PHYINT_OK); /* * At this point, the link is down, or the phyint is suspect, as it * has lost NUM_PROBE_FAILS or more probes. If the phyint does not * belong to any group, this is a PHYINT_FAILURE. Otherwise, continue * on to determine whether this should be considered a PHYINT_FAILURE * or GROUP_FAILURE. */ if (pg == phyint_anongroup) return (PHYINT_FAILURE); /* * Need to compare against other phyints of the same group * to exclude group failures. If the failure was detected via * probing, then if the time of last success (tls) of any * phyint is more recent than the time of first fail (tff) of the * phyint in question, and the link is up on the phyint, * then it is a phyint failure. Otherwise it is a group failure. * If failure was detected via a link down notification sent from * the driver to IP, we see if any phyints in the group are still * running and haven't received a link down notification. We * will usually be processing the link down notification shortly * after it was received, so there is no point looking at the tls * of other phyints. */ retval = GROUP_FAILURE; for (pi2 = pg->pg_phyint; pi2 != NULL; pi2 = pi2->pi_pgnext) { /* Exclude ourself from comparison */ if (pi2 == pi) continue; if (LINK_DOWN(pi)) { /* * We use FLAGS_TO_LINK_STATE() to test the flags * directly, rather then LINK_UP() or LINK_DOWN(), as * we may not have got round to processing the link * state for the other phyints in the group yet. * * The check for PI_RUNNING and group failure handles * the case when the group begins to recover. * PI_RUNNING will be set, and group failure cleared * only after receipt of NUM_PROBE_REPAIRS, by which * time the other phyints should have received at * least 1 packet, and so will not have NUM_PROBE_FAILS. */ if ((pi2->pi_state == PI_RUNNING) && !GROUP_FAILED(pg) && FLAGS_TO_LINK_STATE(pi2)) { retval = PHYINT_FAILURE; break; } continue; } if (LINK_DOWN(pi2)) continue; /* * If there's no probe-based failure detection on this * interface, and its link is still up, then it's still * working and thus the group has not failed. */ if (!PROBE_ENABLED(pi2->pi_v4) && !PROBE_ENABLED(pi2->pi_v6)) { retval = PHYINT_FAILURE; break; } /* * Need to compare against both IPv4 and IPv6 instances. */ pii2 = pi2->pi_v4; if (pii2 != NULL) { probe_success_info(pii2, NULL, &psinfo); if (psinfo.ps_tls_valid) { pi2_tls = psinfo.ps_tls; /* * See comment above regarding check * for PI_RUNNING and group failure. */ if (TIME_GT(pi2_tls, pi_tff) && (pi2->pi_state == PI_RUNNING) && !GROUP_FAILED(pg) && FLAGS_TO_LINK_STATE(pi2)) { retval = PHYINT_FAILURE; break; } } } pii2 = pi2->pi_v6; if (pii2 != NULL) { probe_success_info(pii2, NULL, &psinfo); if (psinfo.ps_tls_valid) { pi2_tls = psinfo.ps_tls; /* * See comment above regarding check * for PI_RUNNING and group failure. */ if (TIME_GT(pi2_tls, pi_tff) && (pi2->pi_state == PI_RUNNING) && !GROUP_FAILED(pg) && FLAGS_TO_LINK_STATE(pi2)) { retval = PHYINT_FAILURE; break; } } } } /* * Update the group state to account for the changes. */ phyint_group_refresh_state(pg); return (retval); } /* * Return the information associated with consecutive probe successes * starting with the most recent probe. At most the last 2 probes can be * in the unacknowledged state. All previous probes have either failed * or succeeded. */ static void probe_success_info(struct phyint_instance *pii, struct target *cur_tg, struct probe_success_count *psinfo) { uint_t i; struct probe_stats *pr_statp; uint_t most_recent; uint_t second_most_recent; boolean_t pi_found_failure = _B_FALSE; boolean_t tg_found_failure = _B_FALSE; uint_t now; uint_t timeout; struct target *tg; if (debug & D_FAILREP) logdebug("probe_success_info(%s)\n", pii->pii_name); bzero(psinfo, sizeof (*psinfo)); now = getcurrenttime(); /* * Start with the most recent probe, and count the number * of consecutive probe successes. Latch the number of successes * on hitting a failure. */ most_recent = PROBE_INDEX_PREV(pii->pii_probe_next); second_most_recent = PROBE_INDEX_PREV(most_recent); for (i = most_recent; i != pii->pii_probe_next; i = PROBE_INDEX_PREV(i)) { pr_statp = &pii->pii_probes[i]; switch (pr_statp->pr_status) { case PR_UNACKED: /* * Only the most recent 2 probes can be unacknowledged */ assert(i == most_recent || i == second_most_recent); tg = pr_statp->pr_target; assert(tg != NULL); /* * The crtt could be zero for some reason, * Eg. the phyint could be failed. If the crtt is * not available use the value of the group's probe * interval which is a worst case estimate. */ timeout = ns2ms(pr_statp->pr_hrtime_start); if (tg->tg_crtt != 0) { timeout += tg->tg_crtt; } else { timeout += pii->pii_phyint->pi_group->pg_probeint; } if (TIME_LT(timeout, now)) { /* * We hit a failure. Latch the total number of * recent consecutive successes. */ pr_statp->pr_time_lost = timeout; probe_chstate(pr_statp, pii, PR_LOST); pi_found_failure = _B_TRUE; if (cur_tg != NULL && tg == cur_tg) { /* * We hit a failure for the desired * target. Latch the number of recent * consecutive successes for this target */ tg_found_failure = _B_TRUE; } } break; case PR_ACKED: /* * Bump up the count of probe successes, if we * have not seen any failure so far. */ if (!pi_found_failure) psinfo->ps_nsucc++; if (cur_tg != NULL && pr_statp->pr_target == cur_tg && !tg_found_failure) { psinfo->ps_nsucc_tg++; } /* * Record the time of last success, if this is * the most recent probe success. */ if (!psinfo->ps_tls_valid) { psinfo->ps_tls = ns2ms(pr_statp->pr_hrtime_ackproc); psinfo->ps_tls_valid = _B_TRUE; } break; case PR_LOST: /* * We hit a failure. Latch the total number of * recent consecutive successes. */ pi_found_failure = _B_TRUE; if (cur_tg != NULL && pr_statp->pr_target == cur_tg) { /* * We hit a failure for the desired target. * Latch the number of recent consecutive * successes for this target */ tg_found_failure = _B_TRUE; } break; default: return; } } } /* * Return the information associated with consecutive probe failures * starting with the most recent probe. Only the last 2 probes can be in the * unacknowledged state. All previous probes have either failed or succeeded. */ static void probe_fail_info(struct phyint_instance *pii, struct target *cur_tg, struct probe_fail_count *pfinfo) { int i; struct probe_stats *pr_statp; boolean_t tg_found_success = _B_FALSE; boolean_t pi_found_success = _B_FALSE; int most_recent; int second_most_recent; uint_t now; uint_t timeout; struct target *tg; if (debug & D_FAILREP) logdebug("probe_fail_info(%s)\n", pii->pii_name); bzero(pfinfo, sizeof (*pfinfo)); now = getcurrenttime(); /* * Start with the most recent probe, and count the number * of consecutive probe failures. Latch the number of failures * on hitting a probe success. */ most_recent = PROBE_INDEX_PREV(pii->pii_probe_next); second_most_recent = PROBE_INDEX_PREV(most_recent); for (i = most_recent; i != pii->pii_probe_next; i = PROBE_INDEX_PREV(i)) { pr_statp = &pii->pii_probes[i]; assert(PR_STATUS_VALID(pr_statp->pr_status)); switch (pr_statp->pr_status) { case PR_UNACKED: /* * Only the most recent 2 probes can be unacknowledged */ assert(i == most_recent || i == second_most_recent); tg = pr_statp->pr_target; /* * Target is guaranteed to exist in the unack. state */ assert(tg != NULL); /* * The crtt could be zero for some reason, * Eg. the phyint could be failed. If the crtt is * not available use the group's probe interval, * which is a worst case estimate. */ timeout = ns2ms(pr_statp->pr_hrtime_start); if (tg->tg_crtt != 0) { timeout += tg->tg_crtt; } else { timeout += pii->pii_phyint->pi_group->pg_probeint; } if (TIME_GT(timeout, now)) break; pr_statp->pr_time_lost = timeout; probe_chstate(pr_statp, pii, PR_LOST); /* FALLTHRU */ case PR_LOST: if (!pi_found_success) { pfinfo->pf_nfail++; pfinfo->pf_tff = pr_statp->pr_time_lost; } if (cur_tg != NULL && pr_statp->pr_target == cur_tg && !tg_found_success) { pfinfo->pf_nfail_tg++; } break; default: /* * We hit a success or unused slot. Latch the * total number of recent consecutive failures. */ pi_found_success = _B_TRUE; if (cur_tg != NULL && pr_statp->pr_target == cur_tg) { /* * We hit a success for the desired target. * Latch the number of recent consecutive * failures for this target */ tg_found_success = _B_TRUE; } } } } /* * Change the state of probe `pr' on phyint_instance `pii' to state `state'. */ void probe_chstate(struct probe_stats *pr, struct phyint_instance *pii, int state) { if (pr->pr_status == state) return; pr->pr_status = state; (void) probe_state_event(pr, pii); } /* * Check if the phyint has been repaired. If no test address has been * configured, then consider the interface repaired if the link is up (unless * the link is flapping; see below). Otherwise, look for proof of probes * being sent and received. If last NUM_PROBE_REPAIRS probes are fine on * either IPv4 or IPv6 instance, the phyint can be considered repaired. */ static boolean_t phyint_repaired(struct phyint *pi) { struct probe_success_count psinfo; struct phyint_instance *pii; struct target *cur_tg; int pr_ndx; uint_t cur_time; if (debug & D_FAILREP) logdebug("phyint_repaired(%s)\n", pi->pi_name); if (LINK_DOWN(pi)) return (_B_FALSE); /* * If we don't have any test addresses and the link is up, then * consider the interface repaired, unless we've received more than * LINK_UP_PERMIN link up notifications in the last minute, in * which case we keep the link down until we drop back below * the threshold. */ if (!PROBE_ENABLED(pi->pi_v4) && !PROBE_ENABLED(pi->pi_v6)) { cur_time = getcurrenttime(); if ((pi->pi_whenup[pi->pi_whendx] == 0 || (cur_time - pi->pi_whenup[pi->pi_whendx]) > MSEC_PERMIN)) { pi->pi_lfmsg_printed = 0; return (_B_TRUE); } if (!pi->pi_lfmsg_printed) { logerr("The link has come up on %s more than %d times " "in the last minute; disabling repair until it " "stabilizes\n", pi->pi_name, LINK_UP_PERMIN); pi->pi_lfmsg_printed = 1; } return (_B_FALSE); } pii = pi->pi_v4; if (PROBE_CAPABLE(pii)) { pr_ndx = PROBE_INDEX_PREV(pii->pii_probe_next); cur_tg = pii->pii_probes[pr_ndx].pr_target; probe_success_info(pii, cur_tg, &psinfo); if (psinfo.ps_nsucc >= NUM_PROBE_REPAIRS || psinfo.ps_nsucc_tg >= NUM_PROBE_REPAIRS) return (_B_TRUE); } pii = pi->pi_v6; if (PROBE_CAPABLE(pii)) { pr_ndx = PROBE_INDEX_PREV(pii->pii_probe_next); cur_tg = pii->pii_probes[pr_ndx].pr_target; probe_success_info(pii, cur_tg, &psinfo); if (psinfo.ps_nsucc >= NUM_PROBE_REPAIRS || psinfo.ps_nsucc_tg >= NUM_PROBE_REPAIRS) return (_B_TRUE); } return (_B_FALSE); } /* * Used to set/clear phyint flags, by making a SIOCSLIFFLAGS call. */ boolean_t change_pif_flags(struct phyint *pi, uint64_t set, uint64_t clear) { int ifsock; struct lifreq lifr; uint64_t old_flags; if (debug & D_FAILREP) { logdebug("change_pif_flags(%s): set %llx clear %llx\n", pi->pi_name, set, clear); } if (pi->pi_v4 != NULL) ifsock = ifsock_v4; else ifsock = ifsock_v6; /* * Get the current flags from the kernel, and set/clear the * desired phyint flags. Since we set only phyint flags, we can * do it on either IPv4 or IPv6 instance. */ (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); if (ioctl(ifsock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) logperror("change_pif_flags: ioctl (get flags)"); return (_B_FALSE); } old_flags = lifr.lifr_flags; lifr.lifr_flags |= set; lifr.lifr_flags &= ~clear; if (old_flags == lifr.lifr_flags) { /* No change in the flags. No need to send ioctl */ return (_B_TRUE); } if (ioctl(ifsock, SIOCSLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) logperror("change_pif_flags: ioctl (set flags)"); return (_B_FALSE); } /* * Keep pi_flags in synch. with actual flags. Assumes flags are * phyint flags. */ pi->pi_flags |= set; pi->pi_flags &= ~clear; if (pi->pi_v4 != NULL) pi->pi_v4->pii_flags = pi->pi_flags; if (pi->pi_v6 != NULL) pi->pi_v6->pii_flags = pi->pi_flags; return (_B_TRUE); } /* * icmp cksum computation for IPv4. */ static int in_cksum(ushort_t *addr, int len) { register int nleft = len; register ushort_t *w = addr; register ushort_t answer; ushort_t odd_byte = 0; register 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); } static void reset_snxt_basetimes(void) { struct phyint_instance *pii; for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { pii->pii_fd_snxt_basetime = pii->pii_snxt_basetime; } } /* * Is the address one of our own addresses? Unfortunately, * we cannot check our phyint tables to determine if the address * is our own. This is because, we don't track interfaces that * are not part of any group. We have to either use a 'bind' or * get the complete list of all interfaces using SIOCGLIFCONF, * to do this check. We could also use SIOCTMYADDR. * Bind fails for the local zone address, so we might include local zone * address as target address. If local zone address is a target address * and it is up, it is not possible to detect the interface failure. * SIOCTMYADDR also doesn't consider local zone address as own address. * So, we choose to use SIOCGLIFCONF to collect the local addresses, and they * are stored in `localaddrs' */ boolean_t own_address(struct in6_addr addr) { addrlist_t *addrp; struct sockaddr_storage ss; int af = IN6_IS_ADDR_V4MAPPED(&addr) ? AF_INET : AF_INET6; addr2storage(af, &addr, &ss); for (addrp = localaddrs; addrp != NULL; addrp = addrp->al_next) { if (sockaddrcmp(&ss, &addrp->al_addr)) return (_B_TRUE); } return (_B_FALSE); } static int ns2ms(int64_t ns) { return (NSEC2MSEC(ns)); } static int64_t tv2ns(struct timeval *tvp) { return (tvp->tv_sec * NANOSEC + tvp->tv_usec * 1000); } /* * 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 "mpd_defs.h" #include "mpd_tables.h" /* * Global list of phyints, phyint instances, phyint groups and the anonymous * group; the latter is initialized in phyint_init(). */ struct phyint *phyints = NULL; struct phyint_instance *phyint_instances = NULL; struct phyint_group *phyint_groups = NULL; struct phyint_group *phyint_anongroup; /* * Grouplist signature; initialized in phyint_init(). */ static uint64_t phyint_grouplistsig; static void phyint_inst_insert(struct phyint_instance *pii); static void phyint_inst_print(struct phyint_instance *pii); static void phyint_insert(struct phyint *pi, struct phyint_group *pg); static void phyint_delete(struct phyint *pi); static boolean_t phyint_is_usable(struct phyint *pi); static void logint_print(struct logint *li); static void logint_insert(struct phyint_instance *pii, struct logint *li); static struct logint *logint_lookup(struct phyint_instance *pii, char *li_name); static void target_print(struct target *tg); static void target_insert(struct phyint_instance *pii, struct target *tg); static struct target *target_first(struct phyint_instance *pii); static struct target *target_select_best(struct phyint_instance *pii); static void target_flush_hosts(struct phyint_group *pg); static void reset_pii_probes(struct phyint_instance *pii, struct target *tg); static boolean_t phyint_inst_v6_sockinit(struct phyint_instance *pii); static boolean_t phyint_inst_v4_sockinit(struct phyint_instance *pii); static int phyint_state_event(struct phyint_group *pg, struct phyint *pi); static int phyint_group_state_event(struct phyint_group *pg); static int phyint_group_change_event(struct phyint_group *pg, ipmp_group_op_t); static int phyint_group_member_event(struct phyint_group *pg, struct phyint *pi, ipmp_if_op_t op); static int logint_upcount(struct phyint *pi); static uint64_t gensig(void); /* Initialize any per-file global state. Returns 0 on success, -1 on failure */ int phyint_init(void) { phyint_grouplistsig = gensig(); if (track_all_phyints) { phyint_anongroup = phyint_group_create(""); if (phyint_anongroup == NULL) return (-1); phyint_group_insert(phyint_anongroup); } return (0); } /* Return the phyint with the given name */ struct phyint * phyint_lookup(const char *name) { struct phyint *pi; if (debug & D_PHYINT) logdebug("phyint_lookup(%s)\n", name); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (strncmp(pi->pi_name, name, sizeof (pi->pi_name)) == 0) break; } return (pi); } /* * Lookup a phyint in the group that has the same hardware address as `pi', or * NULL if there's none. If `online_only' is set, then only online phyints * are considered when matching. Otherwise, phyints that had been offlined * due to a duplicate hardware address will also be considered. */ static struct phyint * phyint_lookup_hwaddr(struct phyint *pi, boolean_t online_only) { struct phyint *pi2; if (pi->pi_group == phyint_anongroup) return (NULL); for (pi2 = pi->pi_group->pg_phyint; pi2 != NULL; pi2 = pi2->pi_pgnext) { if (pi2 == pi) continue; /* * NOTE: even when online_only is B_FALSE, we ignore phyints * that are administratively offline (rather than offline * because they're dups); when they're brought back online, * they'll be flagged as dups if need be. */ if (pi2->pi_state == PI_OFFLINE && (online_only || !pi2->pi_hwaddrdup)) continue; if (pi2->pi_hwaddrlen == pi->pi_hwaddrlen && bcmp(pi2->pi_hwaddr, pi->pi_hwaddr, pi->pi_hwaddrlen) == 0) return (pi2); } return (NULL); } /* * Respond to DLPI notifications. Currently, this only processes physical * address changes for the phyint passed via `arg' by onlining or offlining * phyints in the group. */ /* ARGSUSED */ static void phyint_link_notify(dlpi_handle_t dh, dlpi_notifyinfo_t *dnip, void *arg) { struct phyint *pi = arg; struct phyint *oduppi = NULL, *duppi = NULL; assert((dnip->dni_note & pi->pi_notes) != 0); if (dnip->dni_note != DL_NOTE_PHYS_ADDR) return; assert(dnip->dni_physaddrlen <= DLPI_PHYSADDR_MAX); /* * If our hardware address hasn't changed, there's nothing to do. */ if (pi->pi_hwaddrlen == dnip->dni_physaddrlen && bcmp(pi->pi_hwaddr, dnip->dni_physaddr, pi->pi_hwaddrlen) == 0) return; oduppi = phyint_lookup_hwaddr(pi, _B_FALSE); pi->pi_hwaddrlen = dnip->dni_physaddrlen; (void) memcpy(pi->pi_hwaddr, dnip->dni_physaddr, pi->pi_hwaddrlen); duppi = phyint_lookup_hwaddr(pi, _B_FALSE); if (oduppi != NULL || pi->pi_hwaddrdup) { /* * Our old hardware address was a duplicate. If we'd been * offlined because of it, and our new hardware address is not * a duplicate, then bring us online. Otherwise, `oduppi' * must've been the one brought offline; bring it online. */ if (pi->pi_hwaddrdup) { if (duppi == NULL) (void) phyint_undo_offline(pi); } else { assert(oduppi->pi_hwaddrdup); (void) phyint_undo_offline(oduppi); } } if (duppi != NULL && !pi->pi_hwaddrdup) { /* * Our new hardware address was a duplicate and we're not * yet flagged as a duplicate; bring us offline. */ pi->pi_hwaddrdup = _B_TRUE; (void) phyint_offline(pi, 0); } } /* * Initialize information about the underlying link for `pi', and set us * up to be notified about future changes. Returns _B_TRUE on success. */ boolean_t phyint_link_init(struct phyint *pi) { int retval; uint_t notes; const char *errmsg; dlpi_notifyid_t id; pi->pi_notes = 0; retval = dlpi_open(pi->pi_name, &pi->pi_dh, 0); if (retval != DLPI_SUCCESS) { pi->pi_dh = NULL; errmsg = "cannot open"; goto failed; } pi->pi_hwaddrlen = DLPI_PHYSADDR_MAX; retval = dlpi_get_physaddr(pi->pi_dh, DL_CURR_PHYS_ADDR, pi->pi_hwaddr, &pi->pi_hwaddrlen); if (retval != DLPI_SUCCESS) { errmsg = "cannot get hardware address"; goto failed; } /* * Check if the link supports DLPI link state notifications. For * historical reasons, the actual changes are tracked through routing * sockets, so we immediately disable the notification upon success. */ notes = DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN; retval = dlpi_enabnotify(pi->pi_dh, notes, phyint_link_notify, pi, &id); if (retval == DLPI_SUCCESS) { (void) dlpi_disabnotify(pi->pi_dh, id, NULL); pi->pi_notes |= notes; } /* * Enable notification of hardware address changes to keep pi_hwaddr * up-to-date and track if we need to offline/undo-offline phyints. */ notes = DL_NOTE_PHYS_ADDR; retval = dlpi_enabnotify(pi->pi_dh, notes, phyint_link_notify, pi, &id); if (retval == DLPI_SUCCESS && poll_add(dlpi_fd(pi->pi_dh)) == 0) pi->pi_notes |= notes; return (_B_TRUE); failed: logerr("%s: %s: %s\n", pi->pi_name, errmsg, dlpi_strerror(retval)); if (pi->pi_dh != NULL) { dlpi_close(pi->pi_dh); pi->pi_dh = NULL; } return (_B_FALSE); } /* * Close use of link on `pi'. */ void phyint_link_close(struct phyint *pi) { if (pi->pi_notes & DL_NOTE_PHYS_ADDR) { (void) poll_remove(dlpi_fd(pi->pi_dh)); pi->pi_notes &= ~DL_NOTE_PHYS_ADDR; } /* * NOTE: we don't clear pi_notes here so that iflinkstate() can still * properly report the link state even when offline (which is possible * since we use IFF_RUNNING to track link state). */ dlpi_close(pi->pi_dh); pi->pi_dh = NULL; } /* Return the phyint instance with the given name and the given family */ struct phyint_instance * phyint_inst_lookup(int af, char *name) { struct phyint *pi; if (debug & D_PHYINT) logdebug("phyint_inst_lookup(%s %s)\n", AF_STR(af), name); assert(af == AF_INET || af == AF_INET6); pi = phyint_lookup(name); if (pi == NULL) return (NULL); return (PHYINT_INSTANCE(pi, af)); } struct phyint_group * phyint_group_lookup(const char *pg_name) { struct phyint_group *pg; if (debug & D_PHYINT) logdebug("phyint_group_lookup(%s)\n", pg_name); for (pg = phyint_groups; pg != NULL; pg = pg->pg_next) { if (strncmp(pg->pg_name, pg_name, sizeof (pg->pg_name)) == 0) break; } return (pg); } /* * Insert the phyint in the linked list of all phyints. If the phyint belongs * to some group, insert it in the phyint group list. */ static void phyint_insert(struct phyint *pi, struct phyint_group *pg) { if (debug & D_PHYINT) logdebug("phyint_insert(%s '%s')\n", pi->pi_name, pg->pg_name); /* Insert the phyint at the head of the 'all phyints' list */ pi->pi_next = phyints; pi->pi_prev = NULL; if (phyints != NULL) phyints->pi_prev = pi; phyints = pi; /* * Insert the phyint at the head of the 'phyint_group members' list * of the phyint group to which it belongs. */ pi->pi_pgnext = NULL; pi->pi_pgprev = NULL; pi->pi_group = pg; pi->pi_pgnext = pg->pg_phyint; if (pi->pi_pgnext != NULL) pi->pi_pgnext->pi_pgprev = pi; pg->pg_phyint = pi; /* Refresh the group state now that this phyint has been added */ phyint_group_refresh_state(pg); pg->pg_sig++; (void) phyint_group_member_event(pg, pi, IPMP_IF_ADD); } /* Insert the phyint instance in the linked list of all phyint instances. */ static void phyint_inst_insert(struct phyint_instance *pii) { if (debug & D_PHYINT) { logdebug("phyint_inst_insert(%s %s)\n", AF_STR(pii->pii_af), pii->pii_name); } /* * Insert the phyint at the head of the 'all phyint instances' list. */ pii->pii_next = phyint_instances; pii->pii_prev = NULL; if (phyint_instances != NULL) phyint_instances->pii_prev = pii; phyint_instances = pii; } /* * Create a new phyint with the given parameters. Also insert it into * the list of all phyints and the list of phyint group members by calling * phyint_insert(). */ static struct phyint * phyint_create(char *pi_name, struct phyint_group *pg, uint_t ifindex, uint64_t flags) { struct phyint *pi; pi = calloc(1, sizeof (struct phyint)); if (pi == NULL) { logperror("phyint_create: calloc"); return (NULL); } /* * Record the phyint values. */ (void) strlcpy(pi->pi_name, pi_name, sizeof (pi->pi_name)); pi->pi_taddrthresh = getcurrentsec() + TESTADDR_CONF_TIME; pi->pi_ifindex = ifindex; pi->pi_icmpid = htons(((getpid() & 0xFF) << 8) | (ifindex & 0xFF)); pi->pi_state = PI_INIT; pi->pi_flags = PHYINT_FLAGS(flags); /* * Initialize the link state. The link state is initialized to * up, so that if the link is down when IPMP starts monitoring * the interface, it will appear as though there has been a * transition from the link up to link down. This avoids * having to treat this situation as a special case. */ INIT_LINK_STATE(pi); if (!phyint_link_init(pi)) { free(pi); return (NULL); } /* * Insert the phyint in the list of all phyints, and the * list of phyint group members */ phyint_insert(pi, pg); return (pi); } /* * Create a new phyint instance belonging to the phyint 'pi' and address * family 'af'. Also insert it into the list of all phyint instances by * calling phyint_inst_insert(). */ static struct phyint_instance * phyint_inst_create(struct phyint *pi, int af) { struct phyint_instance *pii; pii = calloc(1, sizeof (struct phyint_instance)); if (pii == NULL) { logperror("phyint_inst_create: calloc"); return (NULL); } /* * Attach the phyint instance to the phyint. * Set the back pointers as well */ pii->pii_phyint = pi; if (af == AF_INET) pi->pi_v4 = pii; else pi->pi_v6 = pii; pii->pii_in_use = 1; pii->pii_probe_sock = -1; pii->pii_snxt = 1; pii->pii_af = af; pii->pii_fd_hrtime = gethrtime() + (FAILURE_DETECTION_QP * (hrtime_t)NANOSEC); pii->pii_flags = pi->pi_flags; /* Insert the phyint instance in the list of all phyint instances. */ phyint_inst_insert(pii); return (pii); } /* * Change the state of phyint `pi' to state `state'. */ void phyint_chstate(struct phyint *pi, enum pi_state state) { /* * To simplify things, some callers always set a given state * regardless of the previous state of the phyint (e.g., setting * PI_RUNNING when it's already set). We shouldn't bother * generating an event or consuming a signature for these, since * the actual state of the interface is unchanged. */ if (pi->pi_state == state) return; pi->pi_state = state; phyint_changed(pi); } /* * Note that `pi' has changed state. */ void phyint_changed(struct phyint *pi) { pi->pi_group->pg_sig++; (void) phyint_state_event(pi->pi_group, pi); } /* * Insert the phyint group in the linked list of all phyint groups * at the head of the list */ void phyint_group_insert(struct phyint_group *pg) { pg->pg_next = phyint_groups; pg->pg_prev = NULL; if (phyint_groups != NULL) phyint_groups->pg_prev = pg; phyint_groups = pg; phyint_grouplistsig++; (void) phyint_group_change_event(pg, IPMP_GROUP_ADD); } /* * Create a new phyint group called 'name'. */ struct phyint_group * phyint_group_create(const char *name) { struct phyint_group *pg; if (debug & D_PHYINT) logdebug("phyint_group_create(%s)\n", name); pg = calloc(1, sizeof (struct phyint_group)); if (pg == NULL) { logperror("phyint_group_create: calloc"); return (NULL); } (void) strlcpy(pg->pg_name, name, sizeof (pg->pg_name)); pg->pg_sig = gensig(); pg->pg_fdt = user_failure_detection_time; pg->pg_probeint = user_probe_interval; pg->pg_in_use = _B_TRUE; /* * Normal groups always start in the PG_FAILED state since they * have no active interfaces. In contrast, anonymous groups are * heterogeneous and thus always PG_OK. */ pg->pg_state = (name[0] == '\0' ? PG_OK : PG_FAILED); return (pg); } /* * Change the state of the phyint group `pg' to state `state'. */ void phyint_group_chstate(struct phyint_group *pg, enum pg_state state) { assert(pg != phyint_anongroup); /* * To simplify things, some callers always set a given state * regardless of the previous state of the group (e.g., setting * PG_DEGRADED when it's already set). We shouldn't bother * generating an event or consuming a signature for these, since * the actual state of the group is unchanged. */ if (pg->pg_state == state) return; pg->pg_state = state; switch (state) { case PG_FAILED: /* * We can never know with certainty that a group has * failed. It is possible that all known targets have * failed simultaneously, and new targets have come up * instead. If the targets are routers then router * discovery will kick in, and we will see the new routers * thru routing socket messages. But if the targets are * hosts, we have to discover it by multicast. So flush * all the host targets. The next probe will send out a * multicast echo request. If this is a group failure, we * will still not see any response, otherwise the group * will be repaired after we get NUM_PROBE_REPAIRS * consecutive unicast replies on any phyint. */ target_flush_hosts(pg); break; case PG_OK: case PG_DEGRADED: break; default: logerr("phyint_group_chstate: invalid group state %d; " "aborting\n", state); abort(); } pg->pg_sig++; (void) phyint_group_state_event(pg); } /* * Create a new phyint instance and initialize it from the values supplied by * the kernel. Always check for ENXIO before logging any error, because the * interface could have vanished after completion of SIOCGLIFCONF. * Return values: * pointer to the phyint instance on success * NULL on failure Eg. if the phyint instance is not found in the kernel */ struct phyint_instance * phyint_inst_init_from_k(int af, char *pi_name) { char pg_name[LIFNAMSIZ + 1]; int ifsock; uint_t ifindex; uint64_t flags; struct lifreq lifr; struct phyint *pi; struct phyint_instance *pii; boolean_t pi_created; struct phyint_group *pg; retry: pii = NULL; pi = NULL; pg = NULL; pi_created = _B_FALSE; if (debug & D_PHYINT) { logdebug("phyint_inst_init_from_k(%s %s)\n", AF_STR(af), pi_name); } assert(af == AF_INET || af == AF_INET6); /* Get the socket for doing ioctls */ ifsock = (af == AF_INET) ? ifsock_v4 : ifsock_v6; /* * Get the interface flags. Ignore virtual interfaces, IPMP * meta-interfaces, point-to-point interfaces, and interfaces * that can't support multicast. */ (void) strlcpy(lifr.lifr_name, pi_name, sizeof (lifr.lifr_name)); if (ioctl(ifsock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror("phyint_inst_init_from_k:" " ioctl (get flags)"); } return (NULL); } flags = lifr.lifr_flags; if (!(flags & IFF_MULTICAST) || (flags & (IFF_VIRTUAL|IFF_IPMP|IFF_POINTOPOINT))) return (NULL); /* * Get the ifindex for recording later in our tables, in case we need * to create a new phyint. */ if (ioctl(ifsock, SIOCGLIFINDEX, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror("phyint_inst_init_from_k: " " ioctl (get lifindex)"); } return (NULL); } ifindex = lifr.lifr_index; /* * Get the phyint group name of this phyint, from the kernel. */ if (ioctl(ifsock, SIOCGLIFGROUPNAME, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror("phyint_inst_init_from_k: " "ioctl (get group name)"); } return (NULL); } (void) strlcpy(pg_name, lifr.lifr_groupname, sizeof (pg_name)); /* * If the phyint is not part of any group, pg_name is the * null string. If 'track_all_phyints' is false, there is no * need to create a phyint. */ if (pg_name[0] == '\0' && !track_all_phyints) { /* * If the IFF_FAILED, IFF_INACTIVE, or IFF_OFFLINE flags are * set, reset them. These flags shouldn't be set if in.mpathd * isn't tracking the interface. */ if ((flags & (IFF_FAILED | IFF_INACTIVE | IFF_OFFLINE))) { lifr.lifr_flags = flags & ~(IFF_FAILED | IFF_INACTIVE | IFF_OFFLINE); if (ioctl(ifsock, SIOCSLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror("phyint_inst_init_from_k:" " ioctl (set flags)"); } } } return (NULL); } /* * We need to create a new phyint instance. We may also need to * create the group if e.g. the SIOCGLIFCONF loop in initifs() found * an underlying interface before it found its IPMP meta-interface. * Note that we keep any created groups even if phyint_inst_from_k() * fails since a group's existence is not dependent on the ability of * in.mpathd to the track the group's interfaces. */ if ((pg = phyint_group_lookup(pg_name)) == NULL) { if ((pg = phyint_group_create(pg_name)) == NULL) { logerr("phyint_inst_init_from_k: cannot create group " "%s\n", pg_name); return (NULL); } phyint_group_insert(pg); } /* * Lookup the phyint. If the phyint does not exist create it. */ pi = phyint_lookup(pi_name); if (pi == NULL) { pi = phyint_create(pi_name, pg, ifindex, flags); if (pi == NULL) { logerr("phyint_inst_init_from_k:" " unable to create phyint %s\n", pi_name); return (NULL); } pi_created = _B_TRUE; } else { /* The phyint exists already. */ assert(pi_created == _B_FALSE); /* * Normally we should see consistent values for the IPv4 and * IPv6 instances, for phyint properties. If we don't, it * means things have changed underneath us, and we should * resync our tables with the kernel. Check whether the * interface index has changed. If so, it is most likely * the interface has been unplumbed and replumbed, * while we are yet to update our tables. Do it now. */ if (pi->pi_ifindex != ifindex) { phyint_inst_delete(PHYINT_INSTANCE(pi, AF_OTHER(af))); goto retry; } assert(PHYINT_INSTANCE(pi, af) == NULL); /* * If the group name seen by the IPv4 and IPv6 instances * are different, it is most likely the groupname has * changed, while we are yet to update our tables. Do it now. */ if (strcmp(pi->pi_group->pg_name, pg_name) != 0) { phyint_inst_delete(PHYINT_INSTANCE(pi, AF_OTHER(af))); goto retry; } } /* * Create a new phyint instance, corresponding to the 'af' * passed in. */ pii = phyint_inst_create(pi, af); if (pii == NULL) { logerr("phyint_inst_init_from_k: unable to create" "phyint inst %s\n", pi->pi_name); if (pi_created) phyint_delete(pi); return (NULL); } /* * NOTE: the change_pif_flags() implementation requires a phyint * instance before it can function, so a number of tasks that would * otherwise be done in phyint_create() are deferred to here. */ if (pi_created) { /* * If the interface is offline, set the state to PI_OFFLINE. * Otherwise, optimistically consider this interface running. * Later (in process_link_state_changes()), we will adjust * this to match the current state of the link. Further, if * test addresses are subsequently assigned, we will * transition to PI_NOTARGETS and then to either PI_RUNNING or * PI_FAILED depending on the probe results. */ if (pi->pi_flags & IFF_OFFLINE) { phyint_chstate(pi, PI_OFFLINE); } else { /* calls phyint_chstate() */ phyint_transition_to_running(pi); } /* * If this a standby phyint, determine whether it should be * IFF_INACTIVE. */ if (pi->pi_flags & IFF_STANDBY) phyint_standby_refresh_inactive(pi); /* * If this phyint does not have a unique hardware address in its * group, offline it. */ if (phyint_lookup_hwaddr(pi, _B_TRUE) != NULL) { pi->pi_hwaddrdup = _B_TRUE; (void) phyint_offline(pi, 0); } } return (pii); } /* * Bind pii_probe_sock to the address associated with pii_probe_logint. * This socket will be used for sending and receiving ICMP/ICMPv6 probes to * targets. Do the common part in this function, and complete the * initializations by calling the protocol specific functions * phyint_inst_v{4,6}_sockinit() respectively. * * Return values: _B_TRUE/_B_FALSE for success or failure respectively. */ boolean_t phyint_inst_sockinit(struct phyint_instance *pii) { boolean_t success; struct phyint_group *pg; if (debug & D_PHYINT) { logdebug("phyint_inst_sockinit(%s %s)\n", AF_STR(pii->pii_af), pii->pii_name); } assert(pii->pii_probe_logint != NULL); assert(pii->pii_probe_logint->li_flags & IFF_UP); assert(pii->pii_probe_logint->li_flags & IFF_NOFAILOVER); assert(pii->pii_af == AF_INET || pii->pii_af == AF_INET6); /* * If the socket is already bound, close pii_probe_sock */ if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_TRUE); /* * If the phyint is not part of a named group and track_all_phyints is * false, simply return. */ pg = pii->pii_phyint->pi_group; if (pg == phyint_anongroup && !track_all_phyints) { if (debug & D_PHYINT) logdebug("phyint_inst_sockinit: no group\n"); return (_B_FALSE); } /* * Initialize the socket by calling the protocol specific function. * If it succeeds, add the socket to the poll list. */ if (pii->pii_af == AF_INET6) success = phyint_inst_v6_sockinit(pii); else success = phyint_inst_v4_sockinit(pii); if (success && (poll_add(pii->pii_probe_sock) == 0)) return (_B_TRUE); /* Something failed, cleanup and return false */ if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_FALSE); return (_B_FALSE); } /* * IPv6 specific part in initializing the pii_probe_sock. This socket is * used to send/receive ICMPv6 probe packets. */ static boolean_t phyint_inst_v6_sockinit(struct phyint_instance *pii) { icmp6_filter_t filter; int hopcount = 1; int off = 0; int on = 1; struct sockaddr_in6 testaddr; int flags; /* * Open a raw socket with ICMPv6 protocol. * * Use IPV6_BOUND_IF to make sure that probes are sent and received on * the specified phyint only. Bind to the test address to ensure that * the responses are sent to the specified phyint. * * Set the hopcount to 1 so that probe packets are not routed. * Disable multicast loopback. Set the receive filter to * receive only ICMPv6 echo replies. */ pii->pii_probe_sock = socket(pii->pii_af, SOCK_RAW, IPPROTO_ICMPV6); if (pii->pii_probe_sock < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: socket"); return (_B_FALSE); } /* * Probes must not block in case of lower layer issues. */ if ((flags = fcntl(pii->pii_probe_sock, F_GETFL, 0)) == -1) { logperror_pii(pii, "phyint_inst_v6_sockinit: fcntl" " F_GETFL"); return (_B_FALSE); } if (fcntl(pii->pii_probe_sock, F_SETFL, flags | O_NONBLOCK) == -1) { logperror_pii(pii, "phyint_inst_v6_sockinit: fcntl" " F_SETFL O_NONBLOCK"); return (_B_FALSE); } bzero(&testaddr, sizeof (testaddr)); testaddr.sin6_family = AF_INET6; testaddr.sin6_port = 0; testaddr.sin6_addr = pii->pii_probe_logint->li_addr; if (bind(pii->pii_probe_sock, (struct sockaddr *)&testaddr, sizeof (testaddr)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: IPv6 bind"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IPV6, IPV6_MULTICAST_IF, (char *)&pii->pii_ifindex, sizeof (uint_t)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " IPV6_MULTICAST_IF"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IPV6, IPV6_BOUND_IF, &pii->pii_ifindex, sizeof (uint_t)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " IPV6_BOUND_IF"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&hopcount, sizeof (hopcount)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " IPV6_UNICAST_HOPS"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&hopcount, sizeof (hopcount)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " IPV6_MULTICAST_HOPS"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&off, sizeof (off)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " IPV6_MULTICAST_LOOP"); return (_B_FALSE); } /* * Filter out so that we only receive ICMP echo replies */ ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ICMP6_ECHO_REPLY, &filter); if (setsockopt(pii->pii_probe_sock, IPPROTO_ICMPV6, ICMP6_FILTER, (char *)&filter, sizeof (filter)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " ICMP6_FILTER"); return (_B_FALSE); } /* Enable receipt of hoplimit */ if (setsockopt(pii->pii_probe_sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, &on, sizeof (on)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " IPV6_RECVHOPLIMIT"); return (_B_FALSE); } /* Enable receipt of timestamp */ if (setsockopt(pii->pii_probe_sock, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof (on)) < 0) { logperror_pii(pii, "phyint_inst_v6_sockinit: setsockopt" " SO_TIMESTAMP"); return (_B_FALSE); } return (_B_TRUE); } /* * IPv4 specific part in initializing the pii_probe_sock. This socket is * used to send/receive ICMPv4 probe packets. */ static boolean_t phyint_inst_v4_sockinit(struct phyint_instance *pii) { struct sockaddr_in testaddr; char char_off = 0; int ttl = 1; char char_ttl = 1; int on = 1; int flags; /* * Open a raw socket with ICMPv4 protocol. * * Use IP_BOUND_IF to make sure that probes are sent and received on * the specified phyint only. Bind to the test address to ensure that * the responses are sent to the specified phyint. * * Set the ttl to 1 so that probe packets are not routed. * Disable multicast loopback. Enable receipt of timestamp. */ pii->pii_probe_sock = socket(pii->pii_af, SOCK_RAW, IPPROTO_ICMP); if (pii->pii_probe_sock < 0) { logperror_pii(pii, "phyint_inst_v4_sockinit: socket"); return (_B_FALSE); } /* * Probes must not block in case of lower layer issues. */ if ((flags = fcntl(pii->pii_probe_sock, F_GETFL, 0)) == -1) { logperror_pii(pii, "phyint_inst_v4_sockinit: fcntl" " F_GETFL"); return (_B_FALSE); } if (fcntl(pii->pii_probe_sock, F_SETFL, flags | O_NONBLOCK) == -1) { logperror_pii(pii, "phyint_inst_v4_sockinit: fcntl" " F_SETFL O_NONBLOCK"); return (_B_FALSE); } bzero(&testaddr, sizeof (testaddr)); testaddr.sin_family = AF_INET; testaddr.sin_port = 0; IN6_V4MAPPED_TO_INADDR(&pii->pii_probe_logint->li_addr, &testaddr.sin_addr); if (bind(pii->pii_probe_sock, (struct sockaddr *)&testaddr, sizeof (testaddr)) < 0) { logperror_pii(pii, "phyint_inst_v4_sockinit: IPv4 bind"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IP, IP_BOUND_IF, &pii->pii_ifindex, sizeof (uint_t)) < 0) { logperror_pii(pii, "phyint_inst_v4_sockinit: setsockopt" " IP_BOUND_IF"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IP, IP_MULTICAST_IF, (char *)&testaddr.sin_addr, sizeof (struct in_addr)) < 0) { logperror_pii(pii, "phyint_inst_v4_sockinit: setsockopt" " IP_MULTICAST_IF"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IP, IP_TTL, (char *)&ttl, sizeof (ttl)) < 0) { logperror_pii(pii, "phyint_inst_v4_sockinit: setsockopt" " IP_TTL"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IP, IP_MULTICAST_LOOP, (char *)&char_off, sizeof (char_off)) == -1) { logperror_pii(pii, "phyint_inst_v4_sockinit: setsockopt" " IP_MULTICAST_LOOP"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, IPPROTO_IP, IP_MULTICAST_TTL, (char *)&char_ttl, sizeof (char_ttl)) == -1) { logperror_pii(pii, "phyint_inst_v4_sockinit: setsockopt" " IP_MULTICAST_TTL"); return (_B_FALSE); } if (setsockopt(pii->pii_probe_sock, SOL_SOCKET, SO_TIMESTAMP, &on, sizeof (on)) < 0) { logperror_pii(pii, "phyint_inst_v4_sockinit: setsockopt" " SO_TIMESTAMP"); return (_B_FALSE); } return (_B_TRUE); } /* * Remove the phyint group from the list of 'all phyint groups' * and free it. */ void phyint_group_delete(struct phyint_group *pg) { /* * The anonymous group always exists, even when empty. */ if (pg == phyint_anongroup) return; if (debug & D_PHYINT) logdebug("phyint_group_delete('%s')\n", pg->pg_name); /* * The phyint group must be empty, and must not have any phyints. * The phyint group must be in the list of all phyint groups */ assert(pg->pg_phyint == NULL); assert(phyint_groups == pg || pg->pg_prev != NULL); if (pg->pg_prev != NULL) pg->pg_prev->pg_next = pg->pg_next; else phyint_groups = pg->pg_next; if (pg->pg_next != NULL) pg->pg_next->pg_prev = pg->pg_prev; pg->pg_next = NULL; pg->pg_prev = NULL; phyint_grouplistsig++; (void) phyint_group_change_event(pg, IPMP_GROUP_REMOVE); addrlist_free(&pg->pg_addrs); free(pg); } /* * Refresh the state of `pg' based on its current members. */ void phyint_group_refresh_state(struct phyint_group *pg) { enum pg_state state; enum pg_state origstate = pg->pg_state; struct phyint *pi, *usablepi; uint_t nif = 0, nusable = 0; /* * Anonymous groups never change state. */ if (pg == phyint_anongroup) return; for (pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext) { nif++; if (phyint_is_usable(pi)) { nusable++; usablepi = pi; } } if (nusable == 0) state = PG_FAILED; else if (nif == nusable) state = PG_OK; else state = PG_DEGRADED; phyint_group_chstate(pg, state); /* * If we're shutting down, skip logging messages since otherwise our * shutdown housecleaning will make us report that groups are unusable. */ if (cleanup_started) return; /* * NOTE: We use pg_failmsg_printed rather than origstate since * otherwise at startup we'll log a "now usable" message when the * first usable phyint is added to an empty group. */ if (state != PG_FAILED && pg->pg_failmsg_printed) { assert(origstate == PG_FAILED); logerr("At least 1 IP interface (%s) in group %s is now " "usable\n", usablepi->pi_name, pg->pg_name); pg->pg_failmsg_printed = _B_FALSE; } else if (origstate != PG_FAILED && state == PG_FAILED) { logerr("All IP interfaces in group %s are now unusable\n", pg->pg_name); pg->pg_failmsg_printed = _B_TRUE; } } /* * Extract information from the kernel about the desired phyint. * Look only for properties of the phyint and not properties of logints. * Take appropriate action on the changes. * Return codes: * PI_OK * The phyint exists in the kernel and matches our knowledge * of the phyint. * PI_DELETED * The phyint has vanished in the kernel. * PI_IFINDEX_CHANGED * The phyint's interface index has changed. * Ask the caller to delete and recreate the phyint. * PI_IOCTL_ERROR * Some ioctl error. Don't change anything. * PI_GROUP_CHANGED * The phyint has changed group. */ int phyint_inst_update_from_k(struct phyint_instance *pii) { struct lifreq lifr; int ifsock; struct phyint *pi; pi = pii->pii_phyint; if (debug & D_PHYINT) { logdebug("phyint_inst_update_from_k(%s %s)\n", AF_STR(pii->pii_af), pi->pi_name); } /* * Get the ifindex from the kernel, for comparison with the * value in our tables. */ (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; ifsock = (pii->pii_af == AF_INET) ? ifsock_v4 : ifsock_v6; if (ioctl(ifsock, SIOCGLIFINDEX, &lifr) < 0) { if (errno == ENXIO) { return (PI_DELETED); } else { logperror_pii(pii, "phyint_inst_update_from_k:" " ioctl (get lifindex)"); return (PI_IOCTL_ERROR); } } if (lifr.lifr_index != pi->pi_ifindex) { /* * The index has changed. Most likely the interface has * been unplumbed and replumbed. Ask the caller to take * appropriate action. */ if (debug & D_PHYINT) { logdebug("phyint_inst_update_from_k:" " old index %d new index %d\n", pi->pi_ifindex, lifr.lifr_index); } return (PI_IFINDEX_CHANGED); } /* * Get the group name from the kernel, for comparison with * the value in our tables. */ if (ioctl(ifsock, SIOCGLIFGROUPNAME, &lifr) < 0) { if (errno == ENXIO) { return (PI_DELETED); } else { logperror_pii(pii, "phyint_inst_update_from_k:" " ioctl (get groupname)"); return (PI_IOCTL_ERROR); } } /* * If the phyint has changed group i.e. if the phyint group name * returned by the kernel is different, ask the caller to delete * and recreate the phyint in the right group */ if (strcmp(lifr.lifr_groupname, pi->pi_group->pg_name) != 0) { /* Groupname has changed */ if (debug & D_PHYINT) { logdebug("phyint_inst_update_from_k:" " groupname change\n"); } return (PI_GROUP_CHANGED); } /* * Get the current phyint flags from the kernel, and determine what * flags have changed by comparing against our tables. Note that the * IFF_INACTIVE processing in initifs() relies on this call to ensure * that IFF_INACTIVE is really still set on the interface. */ if (ioctl(ifsock, SIOCGLIFFLAGS, &lifr) < 0) { if (errno == ENXIO) { return (PI_DELETED); } else { logperror_pii(pii, "phyint_inst_update_from_k: " " ioctl (get flags)"); return (PI_IOCTL_ERROR); } } pi->pi_flags = PHYINT_FLAGS(lifr.lifr_flags); if (pi->pi_v4 != NULL) pi->pi_v4->pii_flags = pi->pi_flags; if (pi->pi_v6 != NULL) pi->pi_v6->pii_flags = pi->pi_flags; /* * Make sure the IFF_FAILED flag is set if and only if we think * the interface should be failed. */ if (pi->pi_flags & IFF_FAILED) { if (pi->pi_state == PI_RUNNING) (void) change_pif_flags(pi, 0, IFF_FAILED); } else { if (pi->pi_state == PI_FAILED) (void) change_pif_flags(pi, IFF_FAILED, IFF_INACTIVE); } /* No change in phyint status */ return (PI_OK); } /* * Delete the phyint. Remove it from the list of all phyints, and the * list of phyint group members. */ static void phyint_delete(struct phyint *pi) { boolean_t active; struct phyint *pi2; struct phyint_group *pg = pi->pi_group; if (debug & D_PHYINT) logdebug("phyint_delete(%s)\n", pi->pi_name); /* Both IPv4 and IPv6 phyint instances must have been deleted. */ assert(pi->pi_v4 == NULL && pi->pi_v6 == NULL); /* * The phyint must belong to a group. */ assert(pg->pg_phyint == pi || pi->pi_pgprev != NULL); /* The phyint must be in the list of all phyints */ assert(phyints == pi || pi->pi_prev != NULL); /* Remove the phyint from the phyint group list */ pg->pg_sig++; (void) phyint_group_member_event(pg, pi, IPMP_IF_REMOVE); if (pi->pi_pgprev == NULL) { /* Phyint is the 1st in the phyint group list */ pg->pg_phyint = pi->pi_pgnext; } else { pi->pi_pgprev->pi_pgnext = pi->pi_pgnext; } if (pi->pi_pgnext != NULL) pi->pi_pgnext->pi_pgprev = pi->pi_pgprev; pi->pi_pgnext = NULL; pi->pi_pgprev = NULL; /* Refresh the group state now that this phyint has been removed */ phyint_group_refresh_state(pg); /* Remove the phyint from the global list of phyints */ if (pi->pi_prev == NULL) { /* Phyint is the 1st in the list */ phyints = pi->pi_next; } else { pi->pi_prev->pi_next = pi->pi_next; } if (pi->pi_next != NULL) pi->pi_next->pi_prev = pi->pi_prev; pi->pi_next = NULL; pi->pi_prev = NULL; /* * See if another phyint in the group had been offlined because * it was a dup of `pi' -- and if so, online it. */ if (!pi->pi_hwaddrdup && (pi2 = phyint_lookup_hwaddr(pi, _B_FALSE)) != NULL) { assert(pi2->pi_hwaddrdup); (void) phyint_undo_offline(pi2); } /* * If the interface was in a named group and was either an active * standby or the last active interface, try to activate another * interface to compensate. */ if (pg != phyint_anongroup) { active = _B_FALSE; for (pi2 = pg->pg_phyint; pi2 != NULL; pi2 = pi2->pi_pgnext) { if (phyint_is_functioning(pi2) && !(pi2->pi_flags & IFF_INACTIVE)) { active = _B_TRUE; break; } } if (!active || (pi->pi_flags & (IFF_STANDBY|IFF_INACTIVE)) == IFF_STANDBY) phyint_activate_another(pi); } phyint_link_close(pi); free(pi); } /* * Offline phyint `pi' if at least `minred' usable interfaces remain in the * group. Returns an IPMP error code. */ int phyint_offline(struct phyint *pi, uint_t minred) { boolean_t was_active; unsigned int nusable = 0; struct phyint *pi2; struct phyint_group *pg = pi->pi_group; /* * Verify that enough usable interfaces in the group would remain. * As a special case, if the group has failed, allow any non-offline * phyints to be offlined. */ if (pg != phyint_anongroup) { for (pi2 = pg->pg_phyint; pi2 != NULL; pi2 = pi2->pi_pgnext) { if (pi2 == pi) continue; if (phyint_is_usable(pi2) || (GROUP_FAILED(pg) && pi2->pi_state != PI_OFFLINE)) nusable++; } } if (nusable < minred) return (IPMP_EMINRED); was_active = ((pi->pi_flags & IFF_INACTIVE) == 0); if (!change_pif_flags(pi, IFF_OFFLINE, IFF_INACTIVE)) return (IPMP_FAILURE); /* * The interface is now offline, so stop probing it. Note that * if_mpadm(8) will down the test addresses, after receiving a * success reply from us. The routing socket message will then make us * close the socket used for sending probes. But it is more logical * that an offlined interface must not be probed, even if it has test * addresses. * * NOTE: stop_probing() also sets PI_OFFLINE. */ stop_probing(pi); /* * If we're offlining the phyint because it has a duplicate hardware * address, print a warning -- and leave the link open so that we can * be notified of hardware address changes that make it usable again. * Otherwise, close the link so that we won't prevent a detach. */ if (pi->pi_hwaddrdup) { logerr("IP interface %s has a hardware address which is not " "unique in group %s; offlining\n", pi->pi_name, pg->pg_name); } else { phyint_link_close(pi); } /* * If this phyint was preventing another phyint with a duplicate * hardware address from being online, bring that one online now. */ if (!pi->pi_hwaddrdup && (pi2 = phyint_lookup_hwaddr(pi, _B_FALSE)) != NULL) { assert(pi2->pi_hwaddrdup); (void) phyint_undo_offline(pi2); } /* * If this interface was active, try to activate another INACTIVE * interface in the group. */ if (was_active) phyint_activate_another(pi); return (IPMP_SUCCESS); } /* * Undo a previous offline of `pi'. Returns an IPMP error code. */ int phyint_undo_offline(struct phyint *pi) { if (pi->pi_state != PI_OFFLINE) { errno = EINVAL; return (IPMP_FAILURE); } /* * If necessary, reinitialize our link information and verify that its * hardware address is still unique across the group. */ if (pi->pi_dh == NULL && !phyint_link_init(pi)) { errno = EIO; return (IPMP_FAILURE); } if (phyint_lookup_hwaddr(pi, _B_TRUE) != NULL) { pi->pi_hwaddrdup = _B_TRUE; return (IPMP_EHWADDRDUP); } if (pi->pi_hwaddrdup) { logerr("IP interface %s now has a unique hardware address in " "group %s; onlining\n", pi->pi_name, pi->pi_group->pg_name); pi->pi_hwaddrdup = _B_FALSE; } if (!change_pif_flags(pi, 0, IFF_OFFLINE)) return (IPMP_FAILURE); /* * While the interface was offline, it may have failed (e.g. the link * may have gone down). phyint_inst_check_for_failure() will have * already set pi_flags with IFF_FAILED, so we can use that to decide * whether the phyint should transition to running. Note that after * we transition to running, we will start sending probes again (if * test addresses are configured), which may also reveal that the * interface is in fact failed. */ if (pi->pi_flags & IFF_FAILED) { phyint_chstate(pi, PI_FAILED); } else { /* calls phyint_chstate() */ phyint_transition_to_running(pi); } /* * Give the requestor time to configure test addresses before * complaining that they're missing. */ pi->pi_taddrthresh = getcurrentsec() + TESTADDR_CONF_TIME; return (IPMP_SUCCESS); } /* * Delete (unlink and free), the phyint instance. */ void phyint_inst_delete(struct phyint_instance *pii) { struct phyint *pi = pii->pii_phyint; assert(pi != NULL); if (debug & D_PHYINT) { logdebug("phyint_inst_delete(%s %s)\n", AF_STR(pii->pii_af), pi->pi_name); } /* * If the phyint instance has associated probe targets * delete all the targets */ while (pii->pii_targets != NULL) target_delete(pii->pii_targets); /* * Delete all the logints associated with this phyint * instance. */ while (pii->pii_logint != NULL) logint_delete(pii->pii_logint); /* * Close the socket used to send probes to targets from this phyint. */ if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_TRUE); /* * Phyint instance must be in the list of all phyint instances. * Remove phyint instance from the global list of phyint instances. */ assert(phyint_instances == pii || pii->pii_prev != NULL); if (pii->pii_prev == NULL) { /* Phyint is the 1st in the list */ phyint_instances = pii->pii_next; } else { pii->pii_prev->pii_next = pii->pii_next; } if (pii->pii_next != NULL) pii->pii_next->pii_prev = pii->pii_prev; pii->pii_next = NULL; pii->pii_prev = NULL; /* * Reset the phyint instance pointer in the phyint. * If this is the last phyint instance (being deleted) on this * phyint, then delete the phyint. */ if (pii->pii_af == AF_INET) pi->pi_v4 = NULL; else pi->pi_v6 = NULL; if (pi->pi_v4 == NULL && pi->pi_v6 == NULL) phyint_delete(pi); free(pii); } static void phyint_inst_print(struct phyint_instance *pii) { struct logint *li; struct target *tg; char abuf[INET6_ADDRSTRLEN]; int most_recent; int i; if (pii->pii_phyint == NULL) { logdebug("pii->pi_phyint NULL can't print\n"); return; } logdebug("\nPhyint instance: %s %s index %u state %x flags %llx " "sock %x in_use %d\n", AF_STR(pii->pii_af), pii->pii_name, pii->pii_ifindex, pii->pii_state, pii->pii_phyint->pi_flags, pii->pii_probe_sock, pii->pii_in_use); for (li = pii->pii_logint; li != NULL; li = li->li_next) logint_print(li); logdebug("\n"); for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) target_print(tg); if (pii->pii_targets == NULL) logdebug("pi_targets NULL\n"); if (pii->pii_target_next != NULL) { logdebug("pi_target_next %s %s\n", AF_STR(pii->pii_af), pr_addr(pii->pii_af, pii->pii_target_next->tg_address, abuf, sizeof (abuf))); } else { logdebug("pi_target_next NULL\n"); } if (pii->pii_rtt_target_next != NULL) { logdebug("pi_rtt_target_next %s %s\n", AF_STR(pii->pii_af), pr_addr(pii->pii_af, pii->pii_rtt_target_next->tg_address, abuf, sizeof (abuf))); } else { logdebug("pi_rtt_target_next NULL\n"); } if (pii->pii_targets != NULL) { most_recent = PROBE_INDEX_PREV(pii->pii_probe_next); i = most_recent; do { if (pii->pii_probes[i].pr_target != NULL) { logdebug("#%d target %s ", i, pr_addr(pii->pii_af, pii->pii_probes[i].pr_target->tg_address, abuf, sizeof (abuf))); } else { logdebug("#%d target NULL ", i); } logdebug("time_start %lld status %d " "time_ackproc %lld time_lost %u", pii->pii_probes[i].pr_hrtime_start, pii->pii_probes[i].pr_status, pii->pii_probes[i].pr_hrtime_ackproc, pii->pii_probes[i].pr_time_lost); i = PROBE_INDEX_PREV(i); } while (i != most_recent); } } /* * Lookup a logint based on the logical interface name, on the given * phyint instance. */ static struct logint * logint_lookup(struct phyint_instance *pii, char *name) { struct logint *li; if (debug & D_LOGINT) { logdebug("logint_lookup(%s, %s)\n", AF_STR(pii->pii_af), name); } for (li = pii->pii_logint; li != NULL; li = li->li_next) { if (strncmp(name, li->li_name, sizeof (li->li_name)) == 0) break; } return (li); } /* * Insert a logint at the head of the list of logints of the given * phyint instance */ static void logint_insert(struct phyint_instance *pii, struct logint *li) { li->li_next = pii->pii_logint; li->li_prev = NULL; if (pii->pii_logint != NULL) pii->pii_logint->li_prev = li; pii->pii_logint = li; li->li_phyint_inst = pii; } /* * Create a new named logint, on the specified phyint instance. */ static struct logint * logint_create(struct phyint_instance *pii, char *name) { struct logint *li; if (debug & D_LOGINT) { logdebug("logint_create(%s %s %s)\n", AF_STR(pii->pii_af), pii->pii_name, name); } li = calloc(1, sizeof (struct logint)); if (li == NULL) { logperror("logint_create: calloc"); return (NULL); } (void) strncpy(li->li_name, name, sizeof (li->li_name)); li->li_name[sizeof (li->li_name) - 1] = '\0'; logint_insert(pii, li); return (li); } /* * Initialize the logint based on the data returned by the kernel. */ void logint_init_from_k(struct phyint_instance *pii, char *li_name) { int ifsock; uint64_t flags; uint64_t saved_flags; struct logint *li; struct lifreq lifr; struct in6_addr test_subnet; struct in6_addr testaddr; int test_subnet_len; struct sockaddr_in6 *sin6; struct sockaddr_in *sin; char abuf[INET6_ADDRSTRLEN]; boolean_t ptp = _B_FALSE; struct in6_addr tgaddr; if (debug & D_LOGINT) { logdebug("logint_init_from_k(%s %s)\n", AF_STR(pii->pii_af), li_name); } /* Get the socket for doing ioctls */ ifsock = (pii->pii_af == AF_INET) ? ifsock_v4 : ifsock_v6; /* * Get the flags from the kernel. Also serves as a check whether * the logical still exists. If it doesn't exist, no need to proceed * any further. li_in_use will make the caller clean up the logint */ (void) strncpy(lifr.lifr_name, li_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(ifsock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { /* Interface may have vanished */ if (errno != ENXIO) { logperror_pii(pii, "logint_init_from_k: " "ioctl (get flags)"); } return; } flags = lifr.lifr_flags; /* * Verified the logint exists. Now lookup the logint in our tables. * If it does not exist, create a new logint. */ li = logint_lookup(pii, li_name); if (li == NULL) { li = logint_create(pii, li_name); if (li == NULL) { /* * Pretend the interface does not exist * in the kernel */ return; } } /* * Update li->li_flags with the new flags, after saving the old * value. This is used later to check what flags has changed and * take any action */ saved_flags = li->li_flags; li->li_flags = flags; /* * Get the address, prefix, prefixlength and update the logint. * Check if anything has changed. If the logint used for the * test address has changed, take suitable action. */ if (ioctl(ifsock, SIOCGLIFADDR, (char *)&lifr) < 0) { /* Interface may have vanished */ if (errno != ENXIO) { logperror_li(li, "logint_init_from_k: (get addr)"); } goto error; } if (pii->pii_af == AF_INET) { sin = (struct sockaddr_in *)&lifr.lifr_addr; IN6_INADDR_TO_V4MAPPED(&sin->sin_addr, &testaddr); } else { sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; testaddr = sin6->sin6_addr; } if (ioctl(ifsock, SIOCGLIFSUBNET, (char *)&lifr) < 0) { /* Interface may have vanished */ if (errno != ENXIO) logperror_li(li, "logint_init_from_k: (get subnet)"); goto error; } if (lifr.lifr_subnet.ss_family == AF_INET6) { sin6 = (struct sockaddr_in6 *)&lifr.lifr_subnet; test_subnet = sin6->sin6_addr; test_subnet_len = lifr.lifr_addrlen; } else { sin = (struct sockaddr_in *)&lifr.lifr_subnet; IN6_INADDR_TO_V4MAPPED(&sin->sin_addr, &test_subnet); test_subnet_len = lifr.lifr_addrlen + (IPV6_ABITS - IP_ABITS); } /* * If this is the logint corresponding to the test address used for * sending probes, then if anything significant has changed we need to * determine the test address again. We ignore changes to the * IFF_FAILED and IFF_RUNNING flags since those happen as a matter of * course. */ if (pii->pii_probe_logint == li) { if (((li->li_flags ^ saved_flags) & ~(IFF_FAILED | IFF_RUNNING)) != 0 || !IN6_ARE_ADDR_EQUAL(&testaddr, &li->li_addr) || (!ptp && !IN6_ARE_ADDR_EQUAL(&test_subnet, &li->li_subnet)) || (!ptp && test_subnet_len != li->li_subnet_len) || (ptp && !IN6_ARE_ADDR_EQUAL(&tgaddr, &li->li_dstaddr))) { /* * Something significant that affects the testaddress * has changed. Redo the testaddress selection later on * in select_test_ifs(). For now do the cleanup and * set pii_probe_logint to NULL. */ if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_TRUE); pii->pii_probe_logint = NULL; } } /* Update the logint with the values obtained from the kernel. */ li->li_addr = testaddr; li->li_in_use = 1; if (ptp) { li->li_dstaddr = tgaddr; li->li_subnet_len = (pii->pii_af == AF_INET) ? IP_ABITS : IPV6_ABITS; } else { li->li_subnet = test_subnet; li->li_subnet_len = test_subnet_len; } if (debug & D_LOGINT) logint_print(li); return; error: logerr("logint_init_from_k: IGNORED %s %s %s addr %s\n", AF_STR(pii->pii_af), pii->pii_name, li->li_name, pr_addr(pii->pii_af, testaddr, abuf, sizeof (abuf))); logint_delete(li); } /* * Delete (unlink and free) a logint. */ void logint_delete(struct logint *li) { struct phyint_instance *pii; pii = li->li_phyint_inst; assert(pii != NULL); if (debug & D_LOGINT) { int af; char abuf[INET6_ADDRSTRLEN]; af = pii->pii_af; logdebug("logint_delete(%s %s %s/%u)\n", AF_STR(af), li->li_name, pr_addr(af, li->li_addr, abuf, sizeof (abuf)), li->li_subnet_len); } /* logint must be in the list of logints */ assert(pii->pii_logint == li || li->li_prev != NULL); /* Remove the logint from the list of logints */ if (li->li_prev == NULL) { /* logint is the 1st in the list */ pii->pii_logint = li->li_next; } else { li->li_prev->li_next = li->li_next; } if (li->li_next != NULL) li->li_next->li_prev = li->li_prev; li->li_next = NULL; li->li_prev = NULL; /* * If this logint is also being used for probing, then close the * associated socket, if it exists. */ if (pii->pii_probe_logint == li) { if (pii->pii_probe_sock != -1) close_probe_socket(pii, _B_TRUE); pii->pii_probe_logint = NULL; } free(li); } static void logint_print(struct logint *li) { char abuf[INET6_ADDRSTRLEN]; int af = li->li_phyint_inst->pii_af; logdebug("logint: %s %s addr %s/%u", AF_STR(af), li->li_name, pr_addr(af, li->li_addr, abuf, sizeof (abuf)), li->li_subnet_len); logdebug("\tFlags: %llx in_use %d\n", li->li_flags, li->li_in_use); } char * pr_addr(int af, struct in6_addr addr, char *abuf, int len) { struct in_addr addr_v4; if (af == AF_INET) { IN6_V4MAPPED_TO_INADDR(&addr, &addr_v4); (void) inet_ntop(AF_INET, (void *)&addr_v4, abuf, len); } else { (void) inet_ntop(AF_INET6, (void *)&addr, abuf, len); } return (abuf); } /* * Fill in the sockaddr_storage pointed to by `ssp' with the IP address * represented by the [`af',`addr'] pair. Needed because in.mpathd internally * stores all addresses as in6_addrs, but we don't want to expose that. */ void addr2storage(int af, const struct in6_addr *addr, struct sockaddr_storage *ssp) { struct sockaddr_in *sinp = (struct sockaddr_in *)ssp; struct sockaddr_in6 *sin6p = (struct sockaddr_in6 *)ssp; assert(af == AF_INET || af == AF_INET6); switch (af) { case AF_INET: (void) memset(sinp, 0, sizeof (*sinp)); sinp->sin_family = AF_INET; IN6_V4MAPPED_TO_INADDR(addr, &sinp->sin_addr); break; case AF_INET6: (void) memset(sin6p, 0, sizeof (*sin6p)); sin6p->sin6_family = AF_INET6; sin6p->sin6_addr = *addr; break; } } /* Lookup target on its address */ struct target * target_lookup(struct phyint_instance *pii, struct in6_addr addr) { struct target *tg; if (debug & D_TARGET) { char abuf[INET6_ADDRSTRLEN]; logdebug("target_lookup(%s %s): addr %s\n", AF_STR(pii->pii_af), pii->pii_name, pr_addr(pii->pii_af, addr, abuf, sizeof (abuf))); } for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { if (IN6_ARE_ADDR_EQUAL(&tg->tg_address, &addr)) break; } return (tg); } /* * Find and return the next active target, for the next probe. * If no active targets are available, return NULL. */ struct target * target_next(struct target *tg) { struct phyint_instance *pii = tg->tg_phyint_inst; struct target *marker = tg; hrtime_t now; now = gethrtime(); /* * Target must be in the list of targets for this phyint * instance. */ assert(pii->pii_targets == tg || tg->tg_prev != NULL); assert(pii->pii_targets != NULL); /* Return the next active target */ do { /* * Go to the next target. If we hit the end, * reset the ptr to the head */ tg = tg->tg_next; if (tg == NULL) tg = pii->pii_targets; assert(TG_STATUS_VALID(tg->tg_status)); switch (tg->tg_status) { case TG_ACTIVE: return (tg); case TG_UNUSED: assert(pii->pii_targets_are_routers); if (pii->pii_ntargets < MAX_PROBE_TARGETS) { /* * Bubble up the unused target to active */ tg->tg_status = TG_ACTIVE; pii->pii_ntargets++; return (tg); } break; case TG_SLOW: assert(pii->pii_targets_are_routers); if (tg->tg_latime + MIN_RECOVERY_TIME < now) { /* * Bubble up the slow target to unused */ tg->tg_status = TG_UNUSED; } break; case TG_DEAD: assert(pii->pii_targets_are_routers); if (tg->tg_latime + MIN_RECOVERY_TIME < now) { /* * Bubble up the dead target to slow */ tg->tg_status = TG_SLOW; tg->tg_latime = now; } break; } } while (tg != marker); return (NULL); } /* * Select the best available target, that is not already TG_ACTIVE, * for the caller. The caller will determine whether it wants to * make the returned target TG_ACTIVE. * The selection order is as follows. * 1. pick a TG_UNSED target, if it exists. * 2. else pick a TG_SLOW target that has recovered, if it exists * 3. else pick any TG_SLOW target, if it exists * 4. else pick a TG_DEAD target that has recovered, if it exists * 5. else pick any TG_DEAD target, if it exists * 6. else return null */ static struct target * target_select_best(struct phyint_instance *pii) { struct target *tg; struct target *slow = NULL; struct target *dead = NULL; struct target *slow_recovered = NULL; struct target *dead_recovered = NULL; hrtime_t now; now = gethrtime(); for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { assert(TG_STATUS_VALID(tg->tg_status)); switch (tg->tg_status) { case TG_UNUSED: return (tg); case TG_SLOW: if (tg->tg_latime + MIN_RECOVERY_TIME < now) { slow_recovered = tg; /* * Promote the slow_recovered to unused */ tg->tg_status = TG_UNUSED; } else { slow = tg; } break; case TG_DEAD: if (tg->tg_latime + MIN_RECOVERY_TIME < now) { dead_recovered = tg; /* * Promote the dead_recovered to slow */ tg->tg_status = TG_SLOW; tg->tg_latime = now; } else { dead = tg; } break; default: break; } } if (slow_recovered != NULL) return (slow_recovered); else if (slow != NULL) return (slow); else if (dead_recovered != NULL) return (dead_recovered); else return (dead); } /* * Some target was deleted. If we don't have even MIN_PROBE_TARGETS * that are active, pick the next best below. */ static void target_activate_all(struct phyint_instance *pii) { struct target *tg; assert(pii->pii_ntargets == 0); assert(pii->pii_target_next == NULL); assert(pii->pii_rtt_target_next == NULL); assert(pii->pii_targets_are_routers); while (pii->pii_ntargets < MIN_PROBE_TARGETS) { tg = target_select_best(pii); if (tg == NULL) { /* We are out of targets */ return; } assert(TG_STATUS_VALID(tg->tg_status)); assert(tg->tg_status != TG_ACTIVE); tg->tg_status = TG_ACTIVE; pii->pii_ntargets++; if (pii->pii_target_next == NULL) { pii->pii_target_next = tg; pii->pii_rtt_target_next = tg; } } } static struct target * target_first(struct phyint_instance *pii) { struct target *tg; for (tg = pii->pii_targets; tg != NULL; tg = tg->tg_next) { assert(TG_STATUS_VALID(tg->tg_status)); if (tg->tg_status == TG_ACTIVE) break; } return (tg); } /* * Create a default target entry. */ void target_create(struct phyint_instance *pii, struct in6_addr addr, boolean_t is_router) { struct target *tg; struct phyint *pi; struct logint *li; if (debug & D_TARGET) { char abuf[INET6_ADDRSTRLEN]; logdebug("target_create(%s %s, %s)\n", AF_STR(pii->pii_af), pii->pii_name, pr_addr(pii->pii_af, addr, abuf, sizeof (abuf))); } /* * If the test address is not yet initialized, do not add * any target, since we cannot determine whether the target * belongs to the same subnet as the test address. */ li = pii->pii_probe_logint; if (li == NULL) return; /* * If there are multiple subnets associated with an interface, then * add the target to this phyint instance only if it belongs to the * same subnet as the test address. This assures us that we will * be able to reach this target through our routing table. */ if (!prefix_equal(li->li_subnet, addr, li->li_subnet_len)) return; if (pii->pii_targets != NULL) { assert(pii->pii_ntargets <= MAX_PROBE_TARGETS); if (is_router) { if (!pii->pii_targets_are_routers) { /* * Prefer router over hosts. Using hosts is a * fallback mechanism, hence delete all host * targets. */ while (pii->pii_targets != NULL) target_delete(pii->pii_targets); } } else { /* * Routers take precedence over hosts. If this * is a router list and we are trying to add a * host, just return. If this is a host list * and if we have sufficient targets, just return */ if (pii->pii_targets_are_routers || pii->pii_ntargets == MAX_PROBE_TARGETS) return; } } tg = calloc(1, sizeof (struct target)); if (tg == NULL) { logperror("target_create: calloc"); return; } tg->tg_phyint_inst = pii; tg->tg_address = addr; tg->tg_in_use = 1; tg->tg_rtt_sa = -1; tg->tg_num_deferred = 0; /* * If this is the first target, set 'pii_targets_are_routers' * The list of targets is either a list of hosts or list or * routers, but not a mix. */ if (pii->pii_targets == NULL) { assert(pii->pii_ntargets == 0); assert(pii->pii_target_next == NULL); assert(pii->pii_rtt_target_next == NULL); pii->pii_targets_are_routers = is_router ? 1 : 0; } if (pii->pii_ntargets == MAX_PROBE_TARGETS) { assert(pii->pii_targets_are_routers); assert(pii->pii_target_next != NULL); assert(pii->pii_rtt_target_next != NULL); tg->tg_status = TG_UNUSED; } else { if (pii->pii_ntargets == 0) { assert(pii->pii_target_next == NULL); pii->pii_target_next = tg; pii->pii_rtt_target_next = tg; } pii->pii_ntargets++; tg->tg_status = TG_ACTIVE; } target_insert(pii, tg); /* * Change state to PI_RUNNING if this phyint instance is capable of * sending and receiving probes -- that is, if we know of at least 1 * target, and this phyint instance is probe-capable. For more * details, see the phyint state diagram in mpd_probe.c. */ pi = pii->pii_phyint; if (pi->pi_state == PI_NOTARGETS && PROBE_CAPABLE(pii)) { if (pi->pi_flags & IFF_FAILED) phyint_chstate(pi, PI_FAILED); else phyint_chstate(pi, PI_RUNNING); } } /* * Add the target address named by `addr' to phyint instance `pii' if it does * not already exist. If the target is a router, `is_router' should be set to * B_TRUE. */ void target_add(struct phyint_instance *pii, struct in6_addr addr, boolean_t is_router) { struct target *tg; if (pii == NULL) return; tg = target_lookup(pii, addr); /* * If the target does not exist, create it; target_create() will set * tg_in_use to true. Even if it exists already, if it's a router * target and we'd previously learned of it through multicast, then we * need to recreate it as a router target. Otherwise, just set * tg_in_use to to true so that init_router_targets() won't delete it. */ if (tg == NULL || (is_router && !pii->pii_targets_are_routers)) target_create(pii, addr, is_router); else if (is_router) tg->tg_in_use = 1; } /* * Insert target at head of linked list of targets for the associated * phyint instance */ static void target_insert(struct phyint_instance *pii, struct target *tg) { tg->tg_next = pii->pii_targets; tg->tg_prev = NULL; if (tg->tg_next != NULL) tg->tg_next->tg_prev = tg; pii->pii_targets = tg; } /* * Delete a target (unlink and free). */ void target_delete(struct target *tg) { int af; struct phyint_instance *pii; struct phyint_instance *pii_other; pii = tg->tg_phyint_inst; af = pii->pii_af; if (debug & D_TARGET) { char abuf[INET6_ADDRSTRLEN]; logdebug("target_delete(%s %s, %s)\n", AF_STR(af), pii->pii_name, pr_addr(af, tg->tg_address, abuf, sizeof (abuf))); } /* * Target must be in the list of targets for this phyint * instance. */ assert(pii->pii_targets == tg || tg->tg_prev != NULL); /* * Reset all references to 'tg' in the probe information * for this phyint. */ reset_pii_probes(pii, tg); /* * Remove this target from the list of targets of this * phyint instance. */ if (tg->tg_prev == NULL) { pii->pii_targets = tg->tg_next; } else { tg->tg_prev->tg_next = tg->tg_next; } if (tg->tg_next != NULL) tg->tg_next->tg_prev = tg->tg_prev; tg->tg_next = NULL; tg->tg_prev = NULL; if (tg->tg_status == TG_ACTIVE) pii->pii_ntargets--; /* * Adjust the next target to probe, if it points to * to the currently deleted target. */ if (pii->pii_target_next == tg) pii->pii_target_next = target_first(pii); if (pii->pii_rtt_target_next == tg) pii->pii_rtt_target_next = target_first(pii); free(tg); /* * The number of active targets pii_ntargets == 0 iff * the next active target pii->pii_target_next == NULL */ if (pii->pii_ntargets != 0) { assert(pii->pii_target_next != NULL); assert(pii->pii_rtt_target_next != NULL); assert(pii->pii_target_next->tg_status == TG_ACTIVE); assert(pii->pii_rtt_target_next->tg_status == TG_ACTIVE); return; } /* At this point, we don't have any active targets. */ assert(pii->pii_target_next == NULL); assert(pii->pii_rtt_target_next == NULL); if (pii->pii_targets_are_routers) { /* * Activate any TG_SLOW or TG_DEAD router targets, * since we don't have any other targets */ target_activate_all(pii); if (pii->pii_ntargets != 0) { assert(pii->pii_target_next != NULL); assert(pii->pii_rtt_target_next != NULL); assert(pii->pii_target_next->tg_status == TG_ACTIVE); assert(pii->pii_rtt_target_next->tg_status == TG_ACTIVE); return; } } /* * If we still don't have any active targets, the list must * must be really empty. There aren't even TG_SLOW or TG_DEAD * targets. Zero out the probe stats since it will not be * relevant any longer. */ assert(pii->pii_targets == NULL); pii->pii_targets_are_routers = _B_FALSE; clear_pii_probe_stats(pii); pii_other = phyint_inst_other(pii); /* * If there are no targets on both instances and the interface would * otherwise be considered PI_RUNNING, go back to PI_NOTARGETS state, * since we cannot probe this phyint any more. For more details, * please see phyint state diagram in mpd_probe.c. */ if (!PROBE_CAPABLE(pii_other) && LINK_UP(pii->pii_phyint) && pii->pii_phyint->pi_state != PI_OFFLINE) phyint_chstate(pii->pii_phyint, PI_NOTARGETS); } /* * Flush the target list of every phyint in the group, if the list * is a host target list. This is called if group failure is suspected. * If all targets have failed, multicast will subsequently discover new * targets. Else it is a group failure. * Note: This function is a no-op if the list is a router target list. */ static void target_flush_hosts(struct phyint_group *pg) { struct phyint *pi; struct phyint_instance *pii; if (debug & D_TARGET) logdebug("target_flush_hosts(%s)\n", pg->pg_name); for (pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext) { pii = pi->pi_v4; if (pii != NULL && !pii->pii_targets_are_routers) { /* * Delete all the targets. When the list becomes * empty, target_delete() will set pii->pii_targets * to NULL. */ while (pii->pii_targets != NULL) target_delete(pii->pii_targets); } pii = pi->pi_v6; if (pii != NULL && !pii->pii_targets_are_routers) { /* * Delete all the targets. When the list becomes * empty, target_delete() will set pii->pii_targets * to NULL. */ while (pii->pii_targets != NULL) target_delete(pii->pii_targets); } } } /* * Reset all references to 'target' in the probe info, as this target is * being deleted. The pr_target field is guaranteed to be non-null if * pr_status is PR_UNACKED. So we change the pr_status to PR_LOST, so that * pr_target will not be accessed unconditionally. */ static void reset_pii_probes(struct phyint_instance *pii, struct target *tg) { int i; for (i = 0; i < PROBE_STATS_COUNT; i++) { if (pii->pii_probes[i].pr_target == tg) { if (pii->pii_probes[i].pr_status == PR_UNACKED) { probe_chstate(&pii->pii_probes[i], pii, PR_LOST); } pii->pii_probes[i].pr_target = NULL; } } } /* * Clear the probe statistics array. */ void clear_pii_probe_stats(struct phyint_instance *pii) { bzero(pii->pii_probes, sizeof (struct probe_stats) * PROBE_STATS_COUNT); /* Reset the next probe index in the probe stats array */ pii->pii_probe_next = 0; } static void target_print(struct target *tg) { char abuf[INET6_ADDRSTRLEN]; char buf[128]; char buf2[128]; int af; int i; af = tg->tg_phyint_inst->pii_af; logdebug("Target on %s %s addr %s\n" "status %d rtt_sa %lld rtt_sd %lld crtt %d tg_in_use %d\n", AF_STR(af), tg->tg_phyint_inst->pii_name, pr_addr(af, tg->tg_address, abuf, sizeof (abuf)), tg->tg_status, tg->tg_rtt_sa, tg->tg_rtt_sd, tg->tg_crtt, tg->tg_in_use); buf[0] = '\0'; for (i = 0; i < tg->tg_num_deferred; i++) { (void) snprintf(buf2, sizeof (buf2), " %dms", tg->tg_deferred[i]); (void) strlcat(buf, buf2, sizeof (buf)); } logdebug("deferred rtts:%s\n", buf); } void phyint_inst_print_all(void) { struct phyint_instance *pii; for (pii = phyint_instances; pii != NULL; pii = pii->pii_next) { phyint_inst_print(pii); } } /* * Compare two prefixes that have the same prefix length. * Fails if the prefix length is unreasonable. */ boolean_t prefix_equal(struct in6_addr p1, struct in6_addr p2, uint_t prefix_len) { uchar_t mask; int j; if (prefix_len > IPV6_ABITS) return (_B_FALSE); for (j = 0; prefix_len > 8; prefix_len -= 8, j++) if (p1.s6_addr[j] != p2.s6_addr[j]) return (_B_FALSE); /* Make the N leftmost bits one */ mask = 0xff << (8 - prefix_len); if ((p1.s6_addr[j] & mask) != (p2.s6_addr[j] & mask)) return (_B_FALSE); return (_B_TRUE); } /* * Get the number of UP logints on phyint `pi'. */ static int logint_upcount(struct phyint *pi) { struct logint *li; int count = 0; if (pi->pi_v4 != NULL) { for (li = pi->pi_v4->pii_logint; li != NULL; li = li->li_next) { if (li->li_flags & IFF_UP) count++; } } if (pi->pi_v6 != NULL) { for (li = pi->pi_v6->pii_logint; li != NULL; li = li->li_next) { if (li->li_flags & IFF_UP) count++; } } return (count); } /* * Get the phyint instance with the other (IPv4 / IPv6) protocol */ struct phyint_instance * phyint_inst_other(struct phyint_instance *pii) { if (pii->pii_af == AF_INET) return (pii->pii_phyint->pi_v6); else return (pii->pii_phyint->pi_v4); } /* * Check whether a phyint is functioning. */ boolean_t phyint_is_functioning(struct phyint *pi) { if (pi->pi_state == PI_RUNNING) return (_B_TRUE); return (pi->pi_state == PI_NOTARGETS && !(pi->pi_flags & IFF_FAILED)); } /* * Check whether a phyint is usable. */ boolean_t phyint_is_usable(struct phyint *pi) { if (logint_upcount(pi) == 0) return (_B_FALSE); return (phyint_is_functioning(pi)); } /* * Post an EC_IPMP sysevent of subclass `subclass' and attributes `nvl'. * Before sending the event, it prepends the current version of the IPMP * sysevent API. Returns 0 on success, -1 on failure (in either case, * `nvl' is freed). */ static int post_event(const char *subclass, nvlist_t *nvl) { static evchan_t *evchp = NULL; /* * Initialize the event channel if we haven't already done so. */ if (evchp == NULL) { errno = sysevent_evc_bind(IPMP_EVENT_CHAN, &evchp, EVCH_CREAT); if (errno != 0) { logerr("cannot create event channel `%s': %s\n", IPMP_EVENT_CHAN, strerror(errno)); goto failed; } } errno = nvlist_add_uint32(nvl, IPMP_EVENT_VERSION, IPMP_EVENT_CUR_VERSION); if (errno != 0) { logerr("cannot create `%s' event: %s", subclass, strerror(errno)); goto failed; } errno = sysevent_evc_publish(evchp, EC_IPMP, subclass, "com.sun", "in.mpathd", nvl, EVCH_NOSLEEP); if (errno != 0) { logerr("cannot send `%s' event: %s\n", subclass, strerror(errno)); goto failed; } nvlist_free(nvl); return (0); failed: nvlist_free(nvl); return (-1); } /* * Return the external IPMP state associated with phyint `pi'. */ static ipmp_if_state_t ifstate(struct phyint *pi) { switch (pi->pi_state) { case PI_INIT: return (IPMP_IF_UNKNOWN); case PI_NOTARGETS: if (pi->pi_flags & IFF_FAILED) return (IPMP_IF_FAILED); return (IPMP_IF_UNKNOWN); case PI_OFFLINE: return (IPMP_IF_OFFLINE); case PI_FAILED: return (IPMP_IF_FAILED); case PI_RUNNING: return (IPMP_IF_OK); } logerr("ifstate: unknown state %d; aborting\n", pi->pi_state); abort(); /* NOTREACHED */ } /* * Return the external IPMP interface type associated with phyint `pi'. */ static ipmp_if_type_t iftype(struct phyint *pi) { if (pi->pi_flags & IFF_STANDBY) return (IPMP_IF_STANDBY); else return (IPMP_IF_NORMAL); } /* * Return the external IPMP link state associated with phyint `pi'. */ static ipmp_if_linkstate_t iflinkstate(struct phyint *pi) { if (!(pi->pi_notes & (DL_NOTE_LINK_UP|DL_NOTE_LINK_DOWN))) return (IPMP_LINK_UNKNOWN); return (LINK_DOWN(pi) ? IPMP_LINK_DOWN : IPMP_LINK_UP); } /* * Return the external IPMP probe state associated with phyint `pi'. */ static ipmp_if_probestate_t ifprobestate(struct phyint *pi) { if (!PROBE_ENABLED(pi->pi_v4) && !PROBE_ENABLED(pi->pi_v6)) return (IPMP_PROBE_DISABLED); if (pi->pi_state == PI_FAILED) return (IPMP_PROBE_FAILED); if (!PROBE_CAPABLE(pi->pi_v4) && !PROBE_CAPABLE(pi->pi_v6)) return (IPMP_PROBE_UNKNOWN); return (IPMP_PROBE_OK); } /* * Return the external IPMP target mode associated with phyint instance `pii'. */ static ipmp_if_targmode_t iftargmode(struct phyint_instance *pii) { if (!PROBE_ENABLED(pii)) return (IPMP_TARG_DISABLED); else if (pii->pii_targets_are_routers) return (IPMP_TARG_ROUTES); else return (IPMP_TARG_MULTICAST); } /* * Return the external IPMP flags associated with phyint `pi'. */ static ipmp_if_flags_t ifflags(struct phyint *pi) { ipmp_if_flags_t flags = 0; if (logint_upcount(pi) == 0) flags |= IPMP_IFFLAG_DOWN; if (pi->pi_flags & IFF_INACTIVE) flags |= IPMP_IFFLAG_INACTIVE; if (pi->pi_hwaddrdup) flags |= IPMP_IFFLAG_HWADDRDUP; if (phyint_is_functioning(pi) && flags == 0) flags |= IPMP_IFFLAG_ACTIVE; return (flags); } /* * Store the test address used on phyint instance `pii' in `ssp'. If there's * no test address, 0.0.0.0 is stored. */ static struct sockaddr_storage * iftestaddr(struct phyint_instance *pii, struct sockaddr_storage *ssp) { if (PROBE_ENABLED(pii)) addr2storage(pii->pii_af, &pii->pii_probe_logint->li_addr, ssp); else addr2storage(AF_INET6, &in6addr_any, ssp); return (ssp); } /* * Return the external IPMP group state associated with phyint group `pg'. */ static ipmp_group_state_t groupstate(struct phyint_group *pg) { switch (pg->pg_state) { case PG_FAILED: return (IPMP_GROUP_FAILED); case PG_DEGRADED: return (IPMP_GROUP_DEGRADED); case PG_OK: return (IPMP_GROUP_OK); } logerr("groupstate: unknown state %d; aborting\n", pg->pg_state); abort(); /* NOTREACHED */ } /* * Return the external IPMP probe state associated with probe `ps'. */ static ipmp_probe_state_t probestate(struct probe_stats *ps) { switch (ps->pr_status) { case PR_UNUSED: case PR_LOST: return (IPMP_PROBE_LOST); case PR_UNACKED: return (IPMP_PROBE_SENT); case PR_ACKED: return (IPMP_PROBE_ACKED); } logerr("probestate: unknown state %d; aborting\n", ps->pr_status); abort(); /* NOTREACHED */ } /* * Generate an ESC_IPMP_PROBE_STATE sysevent for the probe described by `pr' * on phyint instance `pii'. Returns 0 on success, -1 on failure. */ int probe_state_event(struct probe_stats *pr, struct phyint_instance *pii) { nvlist_t *nvl; hrtime_t proc_time = 0, recv_time = 0; struct sockaddr_storage ss; struct target *tg = pr->pr_target; int64_t rttavg, rttdev; errno = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); if (errno != 0) { logperror("cannot create `interface change' event"); return (-1); } errno = nvlist_add_uint32(nvl, IPMP_PROBE_ID, pr->pr_id); if (errno != 0) goto failed; errno = nvlist_add_string(nvl, IPMP_IF_NAME, pii->pii_phyint->pi_name); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_PROBE_STATE, probestate(pr)); if (errno != 0) goto failed; errno = nvlist_add_hrtime(nvl, IPMP_PROBE_START_TIME, pr->pr_hrtime_start); if (errno != 0) goto failed; errno = nvlist_add_hrtime(nvl, IPMP_PROBE_SENT_TIME, pr->pr_hrtime_sent); if (errno != 0) goto failed; if (pr->pr_status == PR_ACKED) { recv_time = pr->pr_hrtime_ackrecv; proc_time = pr->pr_hrtime_ackproc; } errno = nvlist_add_hrtime(nvl, IPMP_PROBE_ACKRECV_TIME, recv_time); if (errno != 0) goto failed; errno = nvlist_add_hrtime(nvl, IPMP_PROBE_ACKPROC_TIME, proc_time); if (errno != 0) goto failed; if (tg != NULL) addr2storage(pii->pii_af, &tg->tg_address, &ss); else addr2storage(pii->pii_af, &in6addr_any, &ss); errno = nvlist_add_byte_array(nvl, IPMP_PROBE_TARGET, (uchar_t *)&ss, sizeof (ss)); if (errno != 0) goto failed; rttavg = (tg != NULL) ? (tg->tg_rtt_sa / 8) : 0; errno = nvlist_add_int64(nvl, IPMP_PROBE_TARGET_RTTAVG, rttavg); if (errno != 0) goto failed; rttdev = (tg != NULL) ? (tg->tg_rtt_sd / 4) : 0; errno = nvlist_add_int64(nvl, IPMP_PROBE_TARGET_RTTDEV, rttdev); if (errno != 0) goto failed; return (post_event(ESC_IPMP_PROBE_STATE, nvl)); failed: logperror("cannot create `probe state' event"); nvlist_free(nvl); return (-1); } /* * Generate an ESC_IPMP_GROUP_STATE sysevent for phyint group `pg'. * Returns 0 on success, -1 on failure. */ static int phyint_group_state_event(struct phyint_group *pg) { nvlist_t *nvl; errno = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); if (errno != 0) { logperror("cannot create `group state change' event"); return (-1); } errno = nvlist_add_string(nvl, IPMP_GROUP_NAME, pg->pg_name); if (errno != 0) goto failed; errno = nvlist_add_uint64(nvl, IPMP_GROUP_SIGNATURE, pg->pg_sig); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_GROUP_STATE, groupstate(pg)); if (errno != 0) goto failed; return (post_event(ESC_IPMP_GROUP_STATE, nvl)); failed: logperror("cannot create `group state change' event"); nvlist_free(nvl); return (-1); } /* * Generate an ESC_IPMP_GROUP_CHANGE sysevent of type `op' for phyint group * `pg'. Returns 0 on success, -1 on failure. */ static int phyint_group_change_event(struct phyint_group *pg, ipmp_group_op_t op) { nvlist_t *nvl; errno = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); if (errno != 0) { logperror("cannot create `group change' event"); return (-1); } errno = nvlist_add_string(nvl, IPMP_GROUP_NAME, pg->pg_name); if (errno != 0) goto failed; errno = nvlist_add_uint64(nvl, IPMP_GROUP_SIGNATURE, pg->pg_sig); if (errno != 0) goto failed; errno = nvlist_add_uint64(nvl, IPMP_GROUPLIST_SIGNATURE, phyint_grouplistsig); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_GROUP_OPERATION, op); if (errno != 0) goto failed; return (post_event(ESC_IPMP_GROUP_CHANGE, nvl)); failed: logperror("cannot create `group change' event"); nvlist_free(nvl); return (-1); } /* * Generate an ESC_IPMP_GROUP_MEMBER_CHANGE sysevent for phyint `pi' in * group `pg'. Returns 0 on success, -1 on failure. */ static int phyint_group_member_event(struct phyint_group *pg, struct phyint *pi, ipmp_if_op_t op) { nvlist_t *nvl; errno = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); if (errno != 0) { logperror("cannot create `group member change' event"); return (-1); } errno = nvlist_add_string(nvl, IPMP_GROUP_NAME, pg->pg_name); if (errno != 0) goto failed; errno = nvlist_add_uint64(nvl, IPMP_GROUP_SIGNATURE, pg->pg_sig); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_IF_OPERATION, op); if (errno != 0) goto failed; errno = nvlist_add_string(nvl, IPMP_IF_NAME, pi->pi_name); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_IF_TYPE, iftype(pi)); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_IF_STATE, ifstate(pi)); if (errno != 0) goto failed; return (post_event(ESC_IPMP_GROUP_MEMBER_CHANGE, nvl)); failed: logperror("cannot create `group member change' event"); nvlist_free(nvl); return (-1); } /* * Generate an ESC_IPMP_IF_CHANGE sysevent for phyint `pi' in group `pg'. * Returns 0 on success, -1 on failure. */ static int phyint_state_event(struct phyint_group *pg, struct phyint *pi) { nvlist_t *nvl; errno = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0); if (errno != 0) { logperror("cannot create `interface change' event"); return (-1); } errno = nvlist_add_string(nvl, IPMP_GROUP_NAME, pg->pg_name); if (errno != 0) goto failed; errno = nvlist_add_uint64(nvl, IPMP_GROUP_SIGNATURE, pg->pg_sig); if (errno != 0) goto failed; errno = nvlist_add_string(nvl, IPMP_IF_NAME, pi->pi_name); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_IF_TYPE, iftype(pi)); if (errno != 0) goto failed; errno = nvlist_add_uint32(nvl, IPMP_IF_STATE, ifstate(pi)); if (errno != 0) goto failed; return (post_event(ESC_IPMP_IF_CHANGE, nvl)); failed: logperror("cannot create `interface change' event"); nvlist_free(nvl); return (-1); } /* * Generate a signature for use. The signature is conceptually divided * into two pieces: a random 16-bit "generation number" and a 48-bit * monotonically increasing integer. The generation number protects * against stale updates to entities (e.g., IPMP groups) that have been * deleted and since recreated. */ static uint64_t gensig(void) { static int seeded = 0; if (seeded == 0) { srand48((long)gethrtime()); seeded++; } return ((uint64_t)lrand48() << 48 | 1); } /* * Store the information associated with group `grname' into a dynamically * allocated structure pointed to by `*grinfopp'. Returns an IPMP error code. */ unsigned int getgroupinfo(const char *grname, ipmp_groupinfo_t **grinfopp) { struct phyint *pi; struct phyint_group *pg; char (*ifs)[LIFNAMSIZ]; unsigned int i, j; unsigned int nif = 0, naddr = 0; lifgroupinfo_t lifgr; addrlist_t *addrp; struct sockaddr_storage *addrs; int fdt = 0; pg = phyint_group_lookup(grname); if (pg == NULL) return (IPMP_EUNKGROUP); /* * Tally up the number of interfaces, allocate an array to hold them, * and insert their names into the array. While we're at it, if any * interface is actually enabled to send probes, save the group fdt. */ for (pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext) nif++; ifs = alloca(nif * sizeof (*ifs)); for (i = 0, pi = pg->pg_phyint; pi != NULL; pi = pi->pi_pgnext, i++) { assert(i < nif); (void) strlcpy(ifs[i], pi->pi_name, LIFNAMSIZ); if (PROBE_ENABLED(pi->pi_v4) || PROBE_ENABLED(pi->pi_v6)) fdt = pg->pg_fdt; } assert(i == nif); /* * If this is the anonymous group, there's no other information to * collect (since there's no IPMP interface). */ if (pg == phyint_anongroup) { *grinfopp = ipmp_groupinfo_create(pg->pg_name, pg->pg_sig, fdt, groupstate(pg), nif, ifs, "", "", "", "", 0, NULL); return (*grinfopp == NULL ? IPMP_ENOMEM : IPMP_SUCCESS); } /* * Grab some additional information about the group from the kernel. * (NOTE: since SIOCGLIFGROUPINFO does not look up by interface name, * we can use ifsock_v4 even for a V6-only group.) */ (void) strlcpy(lifgr.gi_grname, grname, LIFGRNAMSIZ); if (ioctl(ifsock_v4, SIOCGLIFGROUPINFO, &lifgr) == -1) { if (errno == ENOENT) return (IPMP_EUNKGROUP); logperror("getgroupinfo: SIOCGLIFGROUPINFO"); return (IPMP_FAILURE); } /* * Tally up the number of data addresses, allocate an array to hold * them, and insert their values into the array. */ for (addrp = pg->pg_addrs; addrp != NULL; addrp = addrp->al_next) naddr++; addrs = alloca(naddr * sizeof (*addrs)); i = 0; for (addrp = pg->pg_addrs; addrp != NULL; addrp = addrp->al_next) { /* * It's possible to have duplicate addresses (if some are * down). Weed the dups out to avoid confusing consumers. * (If groups start having tons of addresses, we'll need a * better algorithm here.) */ for (j = 0; j < i; j++) { if (sockaddrcmp(&addrs[j], &addrp->al_addr)) break; } if (j == i) { assert(i < naddr); addrs[i++] = addrp->al_addr; } } naddr = i; *grinfopp = ipmp_groupinfo_create(pg->pg_name, pg->pg_sig, fdt, groupstate(pg), nif, ifs, lifgr.gi_grifname, lifgr.gi_m4ifname, lifgr.gi_m6ifname, lifgr.gi_bcifname, naddr, addrs); return (*grinfopp == NULL ? IPMP_ENOMEM : IPMP_SUCCESS); } /* * Store the target information associated with phyint instance `pii' into a * dynamically allocated structure pointed to by `*targinfopp'. Returns an * IPMP error code. */ unsigned int gettarginfo(struct phyint_instance *pii, const char *name, ipmp_targinfo_t **targinfopp) { uint_t ntarg = 0; struct target *tg; struct sockaddr_storage ss; struct sockaddr_storage *targs = NULL; if (PROBE_CAPABLE(pii)) { targs = alloca(pii->pii_ntargets * sizeof (*targs)); tg = pii->pii_target_next; do { if (tg->tg_status == TG_ACTIVE) { assert(ntarg < pii->pii_ntargets); addr2storage(pii->pii_af, &tg->tg_address, &targs[ntarg++]); } if ((tg = tg->tg_next) == NULL) tg = pii->pii_targets; } while (tg != pii->pii_target_next); assert(ntarg == pii->pii_ntargets); } *targinfopp = ipmp_targinfo_create(name, iftestaddr(pii, &ss), iftargmode(pii), ntarg, targs); return (*targinfopp == NULL ? IPMP_ENOMEM : IPMP_SUCCESS); } /* * Store the information associated with interface `ifname' into a dynamically * allocated structure pointed to by `*ifinfopp'. Returns an IPMP error code. */ unsigned int getifinfo(const char *ifname, ipmp_ifinfo_t **ifinfopp) { int retval; struct phyint *pi; ipmp_targinfo_t *targinfo4; ipmp_targinfo_t *targinfo6; pi = phyint_lookup(ifname); if (pi == NULL) return (IPMP_EUNKIF); if ((retval = gettarginfo(pi->pi_v4, pi->pi_name, &targinfo4)) != 0 || (retval = gettarginfo(pi->pi_v6, pi->pi_name, &targinfo6)) != 0) goto out; *ifinfopp = ipmp_ifinfo_create(pi->pi_name, pi->pi_group->pg_name, ifstate(pi), iftype(pi), iflinkstate(pi), ifprobestate(pi), ifflags(pi), targinfo4, targinfo6); retval = (*ifinfopp == NULL ? IPMP_ENOMEM : IPMP_SUCCESS); out: if (targinfo4 != NULL) ipmp_freetarginfo(targinfo4); if (targinfo6 != NULL) ipmp_freetarginfo(targinfo6); return (retval); } /* * Store the current list of IPMP groups into a dynamically allocated * structure pointed to by `*grlistpp'. Returns an IPMP error code. */ unsigned int getgrouplist(ipmp_grouplist_t **grlistpp) { struct phyint_group *pg; char (*groups)[LIFGRNAMSIZ]; unsigned int i, ngroup; /* * Tally up the number of groups, allocate an array to hold them, and * insert their names into the array. */ for (ngroup = 0, pg = phyint_groups; pg != NULL; pg = pg->pg_next) ngroup++; groups = alloca(ngroup * sizeof (*groups)); for (i = 0, pg = phyint_groups; pg != NULL; pg = pg->pg_next, i++) { assert(i < ngroup); (void) strlcpy(groups[i], pg->pg_name, LIFGRNAMSIZ); } assert(i == ngroup); *grlistpp = ipmp_grouplist_create(phyint_grouplistsig, ngroup, groups); return (*grlistpp == NULL ? IPMP_ENOMEM : IPMP_SUCCESS); } /* * Store the address information for `ssp' (in group `grname') into a * dynamically allocated structure pointed to by `*adinfopp'. Returns an IPMP * error code. (We'd call this function getaddrinfo(), but it would conflict * with getaddrinfo(3SOCKET)). */ unsigned int getgraddrinfo(const char *grname, struct sockaddr_storage *ssp, ipmp_addrinfo_t **adinfopp) { int ifsock; addrlist_t *addrp, *addrmatchp = NULL; ipmp_addr_state_t state; const char *binding = ""; struct lifreq lifr; struct phyint_group *pg; if ((pg = phyint_group_lookup(grname)) == NULL) return (IPMP_EUNKADDR); /* * Walk through the data addresses, and find a match. Note that since * some of the addresses may be down, more than one may match. We * prefer an up address (if one exists). */ for (addrp = pg->pg_addrs; addrp != NULL; addrp = addrp->al_next) { if (sockaddrcmp(ssp, &addrp->al_addr)) { addrmatchp = addrp; if (addrmatchp->al_flags & IFF_UP) break; } } if (addrmatchp == NULL) return (IPMP_EUNKADDR); state = (addrmatchp->al_flags & IFF_UP) ? IPMP_ADDR_UP : IPMP_ADDR_DOWN; if (state == IPMP_ADDR_UP) { ifsock = (ssp->ss_family == AF_INET) ? ifsock_v4 : ifsock_v6; (void) strlcpy(lifr.lifr_name, addrmatchp->al_name, LIFNAMSIZ); if (ioctl(ifsock, SIOCGLIFBINDING, &lifr) >= 0) binding = lifr.lifr_binding; } *adinfopp = ipmp_addrinfo_create(ssp, state, pg->pg_name, binding); return (*adinfopp == NULL ? IPMP_ENOMEM : IPMP_SUCCESS); } /* * Store a snapshot of the IPMP subsystem into a dynamically allocated * structure pointed to by `*snapp'. Returns an IPMP error code. */ unsigned int getsnap(ipmp_snap_t **snapp) { ipmp_grouplist_t *grlistp; ipmp_groupinfo_t *grinfop; ipmp_addrinfo_t *adinfop; ipmp_addrlist_t *adlistp; ipmp_ifinfo_t *ifinfop; ipmp_snap_t *snap; struct phyint *pi; unsigned int i, j; int retval; snap = ipmp_snap_create(); if (snap == NULL) return (IPMP_ENOMEM); /* * Add group list. */ retval = getgrouplist(&snap->sn_grlistp); if (retval != IPMP_SUCCESS) goto failed; /* * Add information for each group in the list, along with all of its * data addresses. */ grlistp = snap->sn_grlistp; for (i = 0; i < grlistp->gl_ngroup; i++) { retval = getgroupinfo(grlistp->gl_groups[i], &grinfop); if (retval != IPMP_SUCCESS) goto failed; retval = ipmp_snap_addgroupinfo(snap, grinfop); if (retval != IPMP_SUCCESS) { ipmp_freegroupinfo(grinfop); goto failed; } adlistp = grinfop->gr_adlistp; for (j = 0; j < adlistp->al_naddr; j++) { retval = getgraddrinfo(grinfop->gr_name, &adlistp->al_addrs[j], &adinfop); if (retval != IPMP_SUCCESS) goto failed; retval = ipmp_snap_addaddrinfo(snap, adinfop); if (retval != IPMP_SUCCESS) { ipmp_freeaddrinfo(adinfop); goto failed; } } } /* * Add information for each configured phyint. */ for (pi = phyints; pi != NULL; pi = pi->pi_next) { retval = getifinfo(pi->pi_name, &ifinfop); if (retval != IPMP_SUCCESS) goto failed; retval = ipmp_snap_addifinfo(snap, ifinfop); if (retval != IPMP_SUCCESS) { ipmp_freeifinfo(ifinfop); goto failed; } } *snapp = snap; return (IPMP_SUCCESS); failed: ipmp_snap_free(snap); return (retval); } /* * 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_TABLES_H #define _MPD_TABLES_H #ifdef __cplusplus extern "C" { #endif /* * Terminology: * * phyint: A NIC eg. hme0. This is represented as 'struct phyint' * * phyint instance: A protocol instance of a phyint. Eg. the IPv4 instance of * hme0 or the IPv6 instance of hme0. (struct phyint_instance) * * logint: A logical interface eg. hme0:1 (struct logint) * * phyint_group: A group of phyints i.e. physical interfaces that are * (i) connected to the same level 2 topology e.g. the same ethernet * switch AND * (ii) share the same phyint group name. * Load spreading and failover occur across members of the same phyint group. * phyint group members must be homogeneous. i.e. if a phyint belonging to a * phyint group has a IPv6 protocol instance, then all members of the phyint * group, must have IPv6 protocol instances. (struct phyint_group) */ #define MAXDEFERREDRTT 1 /* Maximum number of deferred rtts */ /* * Status of the phyint, expressed by the return code of failure_state() */ #define PHYINT_OK 0 /* No failure detected */ #define PHYINT_FAILURE 1 /* NIC failure detected */ #define GROUP_FAILURE 2 /* All NICs have failed */ /* * Return values of phyint_inst_update_from_k() */ #define PI_OK 1 /* Phyint matches in the kernel */ #define PI_DELETED 2 /* Phyint has vanished in the kernel */ #define PI_IFINDEX_CHANGED 3 /* Phyint's ifindex has changed */ #define PI_IOCTL_ERROR 4 /* Some ioctl error */ #define PI_GROUP_CHANGED 5 /* The phyint has changed group. */ #define PHYINT_FLAGS(flags) \ (((flags) & (IFF_STANDBY | IFF_INACTIVE | IFF_FAILED | IFF_OFFLINE | \ IFF_RUNNING)) | (handle_link_notifications ? 0 : IFF_RUNNING)) /* A Phyint can have up to 2 instances, the IPv4 and the IPv6 instance */ #define PHYINT_INSTANCE(pi, af) \ ((af) == AF_INET ? (pi)->pi_v4 : (pi)->pi_v6) /* * A phyint instance is probe *enabled* if it has been configured with a * unique probe address (i.e., an IFF_NOFAILOVER address). It is probe * *capable* if it is also able to send probes (i.e., has one or more * targets available). */ #define PROBE_ENABLED(pii) \ (((pii) != NULL) && ((pii)->pii_probe_sock != -1) && \ ((pii)->pii_probe_logint != NULL) && \ (((pii)->pii_probe_logint->li_dupaddr == 0))) #define PROBE_CAPABLE(pii) \ (PROBE_ENABLED(pii) && ((pii)->pii_ntargets != 0)) /* Subtract b from a modulo n. i.e. (a - b) mod n */ #define MOD_SUB(a, b, n) \ ((((a) + (n)) - (b)) % (n)) /* Increment modulo n */ #define MOD_INCR(a, n) \ (((a) + 1) % (n)) /* Decrement modulo n */ #define MOD_DCR(a, n) \ MOD_SUB(a, 1, n) /* * 'index' represents an index into the circular probe stats array of * size PROBE_STATS_COUNT. 0 <= index < PROBE_STATS_COUNT. This is used * to access members of the pii_probes[] array defined in the phyint_instance * structure. */ #define PROBE_INDEX_PREV(index) \ MOD_DCR(index, PROBE_STATS_COUNT) #define PROBE_INDEX_NEXT(index) \ MOD_INCR(index, PROBE_STATS_COUNT) /* * If we receive more than LINK_UP_PERMIN "link up" notifications in a minute, * then don't actually perform the repair operation until we've dropped back * below the threshold (or we have a probe address and our probes indicate * that the link is functioning again). This is to prevent link flapping in * the case where we don't have a probe address. */ #define LINK_UP_PERMIN 2 #define LINK_DOWN(pi) ((pi)->pi_link_state == 0) #define LINK_UP(pi) (!LINK_DOWN(pi)) #define FLAGS_TO_LINK_STATE(pi) (((pi)->pi_flags & IFF_RUNNING) != 0) #define UPDATE_LINK_STATE(pi) ((pi)->pi_link_state = \ FLAGS_TO_LINK_STATE(pi) ? 1 : 0) #define INIT_LINK_STATE(pi) ((pi)->pi_link_state = 1) /* * Phyint group states; see below for the phyint group definition. */ enum pg_state { PG_OK = 1, /* all interfaces in the group are working */ PG_DEGRADED, /* some interfaces in the group are unusable */ PG_FAILED /* all interfaces in the group are unusable */ }; /* * Convenience macro to check if the whole group has failed. */ #define GROUP_FAILED(pg) ((pg)->pg_state == PG_FAILED) /* * A doubly linked list of all phyint groups in the system. * A phyint group is identified by its group name. */ struct phyint_group { char pg_name[LIFGRNAMSIZ]; /* Phyint group name */ struct phyint *pg_phyint; /* List of phyints in this group */ struct phyint_group *pg_next; /* Next phyint group */ struct phyint_group *pg_prev; /* Prev phyint group */ uint64_t pg_sig; /* Current signature of this group */ int pg_probeint; /* Interval between probes */ int pg_fdt; /* Time needed to detect failure */ enum pg_state pg_state; /* Current group state */ boolean_t pg_in_use; /* To detect removed groups */ struct addrlist *pg_addrs; /* Data addresses in this group */ boolean_t pg_failmsg_printed; /* Group failure msg printed */ }; /* * Phyint states; see below for the phyint definition. */ enum pi_state { PI_INIT = 0, /* Phyint is being initialized */ PI_NOTARGETS = 1, /* Phyint has no targets */ PI_RUNNING = 2, /* Phyint is functioning */ PI_FAILED = 3, /* Phyint is failed */ PI_OFFLINE = 4 /* Phyint is offline */ }; /* * Representation of a NIC or a phyint. There is a list of all known phyints. * There is also a list of phyints belonging to a phyint group, one list * per phyint group. */ struct phyint { char pi_name[LIFNAMSIZ + 1]; /* Phyint name eg. le0 */ struct phyint_instance *pi_v4; /* The IPv4 instance */ struct phyint_instance *pi_v6; /* The IPv6 instance */ struct phyint_group *pi_group; /* Pointer to the group */ struct phyint *pi_next; /* List of all phyints */ struct phyint *pi_prev; /* List of all phyints */ struct phyint *pi_pgnext; /* List of phyints in this group */ struct phyint *pi_pgprev; /* List of phyints in this group */ uint_t pi_ifindex; /* interface index */ enum pi_state pi_state; /* State of the phyint */ uint64_t pi_flags; /* Phyint flags from kernel */ uint16_t pi_icmpid; /* icmp id in icmp echo request */ uint64_t pi_taddrthresh; /* time (in secs) to delay logging */ /* about missing test addresses */ dlpi_handle_t pi_dh; /* DLPI handle to underlying link */ uint_t pi_notes; /* enabled DLPI notifications */ uchar_t pi_hwaddr[DLPI_PHYSADDR_MAX]; /* phyint's hw address */ size_t pi_hwaddrlen; /* phyint's hw address length */ /* * The pi_whenup array is a circular buffer of the most recent * times (in milliseconds since some arbitrary point of time in * the past) that the interface was brought up; pi_whendx identifies * the oldest element of the array. */ uint_t pi_whenup[LINK_UP_PERMIN]; unsigned int pi_whendx; uint_t pi_taddrmsg_printed : 1, /* testaddr msg printed */ pi_duptaddrmsg_printed : 1, /* dup testaddr msg printed */ pi_cfgmsg_printed : 1, /* bad config msg printed */ pi_lfmsg_printed : 1, /* link-flapping msg printed */ pi_link_state : 1, /* interface link state */ pi_hwaddrdup : 1; /* disabled due to dup hw address */ }; /* * A doubly linked list of all phyint_instances each of which contains a * doubly linked list of logical interfaces and targets. For eg. if both * IPv4 and IPv6 are used over hme0, we have 2 phyint instances, 1 for each * protocol. */ struct phyint_instance { struct phyint_instance *pii_next; /* List of all phyint insts */ struct phyint_instance *pii_prev; /* List of all phyint insts */ struct phyint *pii_phyint; /* Back pointer to the phyint */ struct target *pii_targets; /* List of targets on this link */ struct logint *pii_probe_logint; /* IFF_NOFAILOVER addr for probing */ struct logint *pii_logint; /* Doubly linked list of logical ifs */ int pii_probe_sock; /* Socket for ICMP Probe packets */ int pii_af; /* Address family */ uint16_t pii_rack; /* highest acknowledged seq number */ uint16_t pii_snxt; /* sequence number of next probe */ uint_t pii_snxt_time; /* actual next probe time that */ /* includes some randomness */ uint_t pii_snxt_basetime; /* strictly periodic base probe time */ /* for all periodic probes */ uint_t pii_fd_snxt_basetime; /* strictly periodic base probe time */ /* for failure detection probes */ hrtime_t pii_fd_hrtime; /* hrtime_t before which we should */ /* not send probes out this pii */ uint64_t pii_flags; /* Phyint flags from kernel */ struct probe_stats { uint_t pr_id; /* Full ID of probe */ struct target *pr_target; /* Probe Target */ uint_t pr_time_lost; /* Time probe declared lost */ struct timeval pr_tv_sent; /* Wall time probe was sent */ hrtime_t pr_hrtime_start; /* hrtime probe op started */ hrtime_t pr_hrtime_sent; /* hrtime probe was sent */ hrtime_t pr_hrtime_ackrecv; /* hrtime probe ack received */ hrtime_t pr_hrtime_ackproc; /* hrtime probe ack processed */ uint_t pr_status; /* probe status as below */ #define PR_UNUSED 0 /* Probe slot unused */ #define PR_UNACKED 1 /* Probe is unacknowledged */ #define PR_ACKED 2 /* Probe has been acknowledged */ #define PR_LOST 3 /* Probe is declared lost */ } pii_probes[PROBE_STATS_COUNT]; uint_t pii_in_use : 1, /* To detect removed phyints */ pii_basetime_inited : 1, /* probe time initialized */ pii_targets_are_routers : 1; /* routers or hosts ? */ uint_t pii_probe_next; /* next index to use in pii_probes[] */ struct target *pii_target_next; /* next target for probing */ struct target *pii_rtt_target_next; /* next target for rtt probes */ int pii_ntargets; /* Number of active targets */ struct stats { /* Cumulative statistics */ uint64_t lost; /* Number of probes lost */ uint64_t acked; /* Number of probes acked */ uint64_t sent; /* Number of probes sent */ uint64_t unknown; /* Number of ambiguous */ /* probe acks */ } pii_cum_stats; }; #define pii_name pii_phyint->pi_name #define pii_ifindex pii_phyint->pi_ifindex #define pii_state pii_phyint->pi_state #define pii_icmpid pii_phyint->pi_icmpid #define PR_STATUS_VALID(status) ((status) <= PR_LOST) /* * A doubly linked list of prefixes or logicals, hanging off the * phyint instance. */ struct logint { struct logint *li_next; /* Next logint of this phyint inst. */ struct logint *li_prev; /* Prev logint of this phyint inst. */ struct phyint_instance *li_phyint_inst; /* Back pointer to phyint inst. */ char li_name[LIFNAMSIZ + 1]; /* name Eg. hme0:1 */ struct in6_addr li_addr; /* IP address */ struct in6_addr li_dstaddr; /* Dst IP address for pointopoint */ struct in6_addr li_subnet; /* prefix / subnet */ uint_t li_subnet_len; /* prefix / subnet length */ uint64_t li_flags; /* IFF_* flags */ uint_t li_in_use : 1, /* flag to detect deleted logints */ li_dupaddr : 1; /* test address is not unique */ }; /* * Doubly-linked list of probe targets on a phyint instance. Probe targets are * usually onlink routers. If no onlink routers can be found, onlink hosts * are used. */ struct target { struct target *tg_next; /* Next target for this phyint inst. */ struct target *tg_prev; /* Prev target for this phyint inst. */ struct phyint_instance *tg_phyint_inst; /* Back pointer to phyint instance */ struct in6_addr tg_address; /* Target IP address */ int tg_status; /* Status of the target below */ #define TG_ACTIVE 1 /* active probe target */ #define TG_UNUSED 2 /* target not in use now */ #define TG_SLOW 3 /* rtt is high - Not in use now */ #define TG_DEAD 4 /* Target is not responding */ hrtime_t tg_latime; /* Target's last active time */ int64_t tg_rtt_sa; /* Scaled RTT average (in ns) */ int64_t tg_rtt_sd; /* Scaled RTT deviation (in ns) */ int tg_crtt; /* Conservative RTT = A + 4D (in ms) */ uint32_t tg_in_use : 1; /* In use flag */ int64_t tg_deferred[MAXDEFERREDRTT + 1]; /* Deferred rtt data points */ int tg_num_deferred; /* Number of deferred rtt data points */ }; #define TG_STATUS_VALID(status) \ (((status) >= TG_ACTIVE) && ((status) <= TG_DEAD)) /* * Statistics about consecutive probe failures are passed around between * functions in this structure. */ struct probe_fail_count { uint_t pf_tff; /* Earliest time of failure in a series */ int pf_nfail; /* Number of consecutive probe failures */ int pf_nfail_tg; /* Number of consecutive probe fails for */ /* some given target 'tg' */ }; /* * Statistics about consecutive probe successes is passed around between * functions in this structure. */ struct probe_success_count { uint_t ps_tls; /* Most recent time of probe success */ boolean_t ps_tls_valid; /* is ps_tls valid */ int ps_nsucc; /* Number of consecutive probe successes */ /* starting from the most recent */ int ps_nsucc_tg; /* Number of consecutive probe successes */ /* for some given target 'tg' */ }; /* * Statistics about missed probes that were never sent. * Happens due to scheduling delay. */ struct probes_missed { uint_t pm_nprobes; /* Cumulative number of missed probes */ uint_t pm_ntimes; /* Total number of occasions */ }; typedef struct addrlist { struct addrlist *al_next; /* next address */ char al_name[LIFNAMSIZ]; /* address lif name */ uint64_t al_flags; /* address flags */ struct sockaddr_storage al_addr; /* address */ } addrlist_t; /* * Globals */ extern addrlist_t *localaddrs; /* List of all local addresses, including local zones */ extern struct phyint *phyints; /* List of all phyints */ extern struct phyint_group *phyint_groups; /* List of all phyint groups */ extern struct phyint_group *phyint_anongroup; /* Pointer to the anon group */ extern struct phyint_instance *phyint_instances; /* List of all phyint instances */ extern struct probes_missed probes_missed; /* statistics about missed probes */ /* * Function prototypes */ extern int phyint_init(void); extern struct phyint *phyint_lookup(const char *name); extern struct phyint_instance *phyint_inst_lookup(int af, char *name); extern struct phyint_instance *phyint_inst_init_from_k(int af, char *name); extern struct phyint_instance *phyint_inst_other(struct phyint_instance *pii); extern int phyint_inst_update_from_k(struct phyint_instance *pii); extern void phyint_inst_delete(struct phyint_instance *pii); extern uint_t phyint_inst_timer(struct phyint_instance *pii); extern boolean_t phyint_inst_sockinit(struct phyint_instance *pii); extern void phyint_changed(struct phyint *pi); extern void phyint_chstate(struct phyint *pi, enum pi_state state); extern void phyint_group_chstate(struct phyint_group *pg, enum pg_state state); extern struct phyint_group *phyint_group_create(const char *pg_name); extern struct phyint_group *phyint_group_lookup(const char *pg_name); extern void phyint_group_insert(struct phyint_group *pg); extern void phyint_group_delete(struct phyint_group *pg); extern void phyint_group_refresh_state(struct phyint_group *pg); extern void phyint_standby_refresh_inactive(struct phyint *pi); extern void phyint_check_for_repair(struct phyint *pi); extern void phyint_transition_to_running(struct phyint *pi); extern void phyint_activate_another(struct phyint *pi); extern int phyint_offline(struct phyint *pi, unsigned int); extern int phyint_undo_offline(struct phyint *pi); extern boolean_t phyint_is_functioning(struct phyint *pi); extern void logint_init_from_k(struct phyint_instance *pii, char *li_name); extern void logint_delete(struct logint *li); extern struct target *target_lookup(struct phyint_instance *pii, struct in6_addr addr); extern void target_create(struct phyint_instance *pii, struct in6_addr addr, boolean_t is_router); extern void target_delete(struct target *tg); extern struct target *target_next(struct target *tg); extern void target_add(struct phyint_instance *pii, struct in6_addr addr, boolean_t is_router); extern void in_data(struct phyint_instance *pii); extern void in6_data(struct phyint_instance *pii); extern void logperror_pii(struct phyint_instance *pii, const char *str); extern void logperror_li(struct logint *li, const char *str); extern char *pr_addr(int af, struct in6_addr addr, char *abuf, int len); extern void addr2storage(int af, const struct in6_addr *addr, struct sockaddr_storage *ssp); extern void phyint_inst_print_all(void); extern boolean_t prefix_equal(struct in6_addr, struct in6_addr, uint_t); extern void reset_crtt_all(struct phyint *pi); extern int failure_state(struct phyint_instance *pii); extern void process_link_state_changes(void); extern void clear_pii_probe_stats(struct phyint_instance *pii); extern void start_timer(struct phyint_instance *pii); extern void stop_probing(struct phyint *pi); extern boolean_t own_address(struct in6_addr addr); extern boolean_t change_pif_flags(struct phyint *pi, uint64_t set, uint64_t clear); extern void close_probe_socket(struct phyint_instance *pii, boolean_t flag); extern int probe_state_event(struct probe_stats *, struct phyint_instance *); extern void probe_chstate(struct probe_stats *, struct phyint_instance *, int); extern unsigned int getgraddrinfo(const char *, struct sockaddr_storage *, ipmp_addrinfo_t **); extern unsigned int getifinfo(const char *, ipmp_ifinfo_t **); extern unsigned int getgroupinfo(const char *, ipmp_groupinfo_t **); extern unsigned int getgrouplist(ipmp_grouplist_t **); extern unsigned int getsnap(ipmp_snap_t **); extern boolean_t addrlist_add(addrlist_t **, const char *, uint64_t, struct sockaddr_storage *); extern void addrlist_free(addrlist_t **); #ifdef __cplusplus } #endif #endif /* _MPD_TABLES_H */ #!/sbin/sh # # # This file and its contents are supplied under the terms of the # Common Development and Distribution License ("CDDL"), version 1.0. # You may only use this file in accordance with the terms of version # 1.0 of the CDDL. # # A full copy of the text of the CDDL should have accompanied this # source. A copy of the CDDL is also available via the Internet at # http://www.illumos.org/license/CDDL. # # # Copyright 2021 Tintri by DDN, Inc. All rights reserved. # . /lib/svc/share/smf_include.sh if [ -z "$SMF_FMRI" ]; then echo "this script can only be invoked by smf(7)" exit $SMF_EXIT_ERR_NOSMF fi smf_configure_ip || exit $SMF_EXIT_NODAEMON if /lib/inet/in.mpathd; then exit $SMF_EXIT_OK else exit $SMF_EXIT_ERR_FATAL fi # # 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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG= in.ndpd OBJS= config.o main.o ndp.o tables.o trace.o SVCMETHOD= svc-ndp MANIFEST= ndp.xml include ../../../Makefile.cmd ROOTMANIFESTDIR= $(ROOTSVCNETWORKROUTING) # in.ndpd uses the ancillary data feature which is available only through # UNIX 98 standards version of Socket interface. This interface is supposed to # be accessed by -lxnet. In addition -lsocket is used to # capture new not-yet-standard interfaces. Someday -lxnet alone should be enough # when IPv6 inspired new interfaces are part of standards. LDLIBS += -ldhcpagent -lxnet -lsocket -lipadm # these #defines are required to use UNIX 98 interfaces _D_UNIX98_EXTN= -D_XOPEN_SOURCE=500 -D__EXTENSIONS__ $(OBJS) : CPPFLAGS += $(_D_UNIX98_EXTN) CERRWARN += -Wno-switch CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off CTFCONVERT_HOOK = && $(CTFCONVERT_O) CTFMERGE_HOOK = && $(CTFMERGE) -L VERSION -o $@ $(OBJS) $(OBJS) : CFLAGS += $(CTF_FLAGS) .KEEP_STATE: .PARALLEL: $(OBJS) all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(CTFMERGE_HOOK) $(POST_PROCESS) include ../Makefile.lib install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) 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 2007 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "defs.h" #include "tables.h" /* * Parse the config file which consists of entries of the form: * ifdefault [ ]* * prefixdefault [ ]* * if [ ]* * prefix / [ ]* * * All "ifdefault" and "prefixdefault" entries must preceed any * "if" and "prefix" entries. * * Values (such as expiry dates) which contain white space * can be quoted with single or double quotes. */ /* maximum length of messages we send to syslog */ #define NDPD_LOGMSGSIZE 1024 typedef boolean_t (*pfb_t)(char *, uint_t *); struct configinfo { char *ci_name; uint_t ci_min; /* 0: no min check */ uint_t ci_max; /* ~0U: no max check */ uint_t ci_default; uint_t ci_index; /* Into result array */ pfb_t ci_parsefunc; /* Parse function returns -1 on failure */ }; enum config_type { CONFIG_IF, CONFIG_PREFIX}; typedef enum config_type config_type_t; static void set_protocol_defaults(void); static void print_defaults(void); static void parse_var_value(config_type_t, struct configinfo *, char *, char *, struct confvar *); static void parse_default(config_type_t, struct configinfo *, char **, int, struct confvar *); static void parse_if(struct configinfo *, char **, int); static void parse_prefix(struct configinfo *, char **, int); static boolean_t parse_onoff(char *, uint_t *); /* boolean */ static boolean_t parse_int(char *, uint_t *); /* integer */ static boolean_t parse_ms(char *, uint_t *); /* milliseconds */ static boolean_t parse_s(char *, uint_t *); /* seconds */ static boolean_t parse_date(char *, uint_t *); /* date format */ static void conferr(char *fmt, ...); static FILE *open_conffile(char *filename); static int parse_line(char *line, char *argvec[], int argcount); static int readline(FILE *fp, char *line, int length); static int parse_addrprefix(char *strin, struct in6_addr *in6); /* * Per interface configuration variables. * Min, max, and default values are from RFC 2461. */ static struct configinfo iflist[] = { /* Name, Min, Max, Default, Index */ { "DupAddrDetectTransmits", 0, 100, 1, I_DupAddrDetectTransmits, parse_int }, { "AdvSendAdvertisements", 0, 1, 0, I_AdvSendAdvertisements, parse_onoff }, { "MaxRtrAdvInterval", 4, 1800, 600, I_MaxRtrAdvInterval, parse_s }, { "MinRtrAdvInterval", 3, 1350, 200, I_MinRtrAdvInterval, parse_s }, /* * No greater than .75 * MaxRtrAdvInterval. * Default: 0.33 * MaxRtrAdvInterval */ { "AdvManagedFlag", 0, 1, 0, I_AdvManagedFlag, parse_onoff }, { "AdvOtherConfigFlag", 0, 1, 0, I_AdvOtherConfigFlag, parse_onoff }, { "AdvLinkMTU", IPV6_MIN_MTU, 65535, 0, I_AdvLinkMTU, parse_int }, { "AdvReachableTime", 0, 3600000, 0, I_AdvReachableTime, parse_ms }, { "AdvRetransTimer", 0, ~0U, 0, I_AdvRetransTimer, parse_ms }, { "AdvCurHopLimit", 0, 255, 0, I_AdvCurHopLimit, parse_int }, { "AdvDefaultLifetime", 0, 9000, 1800, I_AdvDefaultLifetime, parse_s }, /* * MUST be either zero or between MaxRtrAdvInterval and 9000 seconds. * Default: 3 * MaxRtrAdvInterval */ { "StatelessAddrConf", 0, 1, 1, I_StatelessAddrConf, parse_onoff }, { "StatefulAddrConf", 0, 1, 1, I_StatefulAddrConf, parse_onoff }, /* * Tmp* variables from RFC 3041, where defaults are defined. */ { "TmpAddrsEnabled", 0, 1, 0, I_TmpAddrsEnabled, parse_onoff }, { "TmpValidLifetime", 0, ~0U, 604800, I_TmpValidLifetime, parse_s }, { "TmpPreferredLifetime", 0, ~0U, 86400, I_TmpPreferredLifetime, parse_s }, { "TmpRegenAdvance", 0, 60, 5, I_TmpRegenAdvance, parse_s }, { "TmpMaxDesyncFactor", 0, 600, 600, I_TmpMaxDesyncFactor, parse_s }, { NULL, 0, 0, 0, 0 } }; /* * Per prefix: AdvPrefixList configuration variables. * Min, max, and default values are from RFC 2461. */ static struct configinfo prefixlist[] = { /* Name, Min, Max, Default, Index */ { "AdvValidLifetime", 0, ~0U, 2592000, I_AdvValidLifetime, parse_s }, { "AdvOnLinkFlag", 0, 1, 1, I_AdvOnLinkFlag, parse_onoff }, { "AdvPreferredLifetime", 0, ~0U, 604800, I_AdvPreferredLifetime, parse_s}, { "AdvAutonomousFlag", 0, 1, 1, I_AdvAutonomousFlag, parse_onoff }, { "AdvValidExpiration", 0, ~0U, 0, I_AdvValidExpiration, parse_date }, { "AdvPreferredExpiration", 0, ~0U, 0, I_AdvPreferredExpiration, parse_date}, { NULL, 0, 0, 0, 0 }, }; /* * Data structures used to merge above protocol defaults * with defaults specified in the configuration file. * ifdefault is not static because new interfaces can be * created outside of the configuration context. */ struct confvar ifdefaults[I_IFSIZE]; static struct confvar prefixdefaults[I_PREFIXSIZE]; static char conf_filename[MAXPATHLEN]; static int lineno; /* * Checks for violations of section 5.5.3 (c) of RFC 2462. */ static void check_var_consistency(struct confvar *cv, void *save, int size) { boolean_t rollback = _B_FALSE; int prefl, prefe, valid; prefl = cv[I_AdvPreferredLifetime].cf_value; prefe = cv[I_AdvPreferredExpiration].cf_value; valid = cv[I_AdvValidLifetime].cf_value; if (prefl > valid) { conferr("AdvPreferredLifetime (%u) is greater than " "valid lifetime (%u)\n", prefl, valid); rollback = _B_TRUE; } if (prefe > valid) { conferr("AdvPreferredExpiration (%u) is greater than " "valid lifetime (%u)\n", prefe, valid); rollback = _B_TRUE; } if (rollback) { (void) memcpy(cv, save, size); } } /* * Check for invalid lifetime values for RFC3041 addresses */ static void check_if_var_consistency(struct confvar *cv, void *save, int size) { boolean_t rollback = _B_FALSE; int tpref, tvalid, tdesync, tregen; tpref = cv[I_TmpPreferredLifetime].cf_value; tvalid = cv[I_TmpValidLifetime].cf_value; tdesync = cv[I_TmpMaxDesyncFactor].cf_value; tregen = cv[I_TmpRegenAdvance].cf_value; /* * Only need to do this if tmp addrs are enabled. */ if (cv[I_TmpAddrsEnabled].cf_value == 0) return; if (tdesync > tpref) { conferr("TmpDesyncFactor (%u) is greater than " "TmpPreferredLifetime (%u)\n", tdesync, tpref); rollback = _B_TRUE; } if (tpref > tvalid) { conferr("TmpPreferredLifetime (%u) is greater than " "TmpValidLifetime (%u)\n", tpref, tvalid); rollback = _B_TRUE; } if (tregen > tvalid) { conferr("TmpRegenAdvance (%u) is greater than " "TmpValidLifetime (%u)\n", tregen, tvalid); rollback = _B_TRUE; } if (rollback) { (void) memcpy(cv, save, size); } } int parse_config(char *config_file, boolean_t file_required) { FILE *fp; char line[MAXLINELEN]; char pline[MAXLINELEN]; int argcount; char *argvec[MAXARGSPERLINE]; int defaultdone = 0; /* Set when first non-default command found */ if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_config()\n"); set_protocol_defaults(); if (debug & D_DEFAULTS) print_defaults(); fp = open_conffile(config_file); if (fp == NULL) { if (errno == ENOENT && !file_required) return (0); logperror(config_file); return (-1); } while (readline(fp, line, sizeof (line)) != 0) { (void) strncpy(pline, line, sizeof (pline)); pline[sizeof (pline) - 1] = '\0'; /* NULL terminate */ argcount = parse_line(pline, argvec, sizeof (argvec) / sizeof (argvec[0])); if (debug & D_PARSE) { int i; logmsg(LOG_DEBUG, "scanned %d args\n", argcount); for (i = 0; i < argcount; i++) logmsg(LOG_DEBUG, "arg[%d]: %s\n", i, argvec[i]); } if (argcount == 0) { /* Empty line - or comment only line */ continue; } if (strcmp(argvec[0], "ifdefault") == 0) { char save[sizeof (ifdefaults)]; if (defaultdone) { conferr("ifdefault after non-default " "command\n"); continue; } /* * Save existing values in case what we read is * invalid and we need to restore previous settings. */ (void) memcpy(save, ifdefaults, sizeof (ifdefaults)); parse_default(CONFIG_IF, iflist, argvec+1, argcount-1, ifdefaults); check_if_var_consistency(ifdefaults, save, sizeof (save)); } else if (strcmp(argvec[0], "prefixdefault") == 0) { char save[sizeof (prefixdefaults)]; if (defaultdone) { conferr("prefixdefault after non-default " "command\n"); continue; } /* * Save existing values in case what we read is * invalid and we need to restore previous settings. */ (void) memcpy(save, prefixdefaults, sizeof (prefixdefaults)); parse_default(CONFIG_PREFIX, prefixlist, argvec+1, argcount-1, prefixdefaults); check_var_consistency(prefixdefaults, save, sizeof (save)); } else if (strcmp(argvec[0], "if") == 0) { defaultdone = 1; parse_if(iflist, argvec+1, argcount-1); } else if (strcmp(argvec[0], "prefix") == 0) { defaultdone = 1; parse_prefix(prefixlist, argvec+1, argcount-1); } else { conferr("Unknown command: %s\n", argvec[0]); } } (void) fclose(fp); if (debug & D_DEFAULTS) print_defaults(); return (0); } /* * Extract the defaults from the configinfo tables to initialize * the ifdefaults and prefixdefaults arrays. * The arrays are needed to track which defaults have been changed * by the config file. */ static void set_protocol_defaults(void) { struct configinfo *cip; if (debug & D_DEFAULTS) logmsg(LOG_DEBUG, "extract_protocol_defaults\n"); for (cip = iflist; cip->ci_name != NULL; cip++) { ifdefaults[cip->ci_index].cf_value = cip->ci_default; ifdefaults[cip->ci_index].cf_notdefault = _B_FALSE; } for (cip = prefixlist; cip->ci_name != NULL; cip++) { prefixdefaults[cip->ci_index].cf_value = cip->ci_default; prefixdefaults[cip->ci_index].cf_notdefault = _B_FALSE; } } void print_iflist(struct confvar *confvar) { struct configinfo *cip; for (cip = iflist; cip->ci_name != NULL; cip++) { logmsg(LOG_DEBUG, "\t%s min %u max %u def %u value %u set %d\n", cip->ci_name, cip->ci_min, cip->ci_max, cip->ci_default, confvar[cip->ci_index].cf_value, confvar[cip->ci_index].cf_notdefault); } } void print_prefixlist(struct confvar *confvar) { struct configinfo *cip; for (cip = prefixlist; cip->ci_name != NULL; cip++) { logmsg(LOG_DEBUG, "\t%s min %u max %u def %u value %u set %d\n", cip->ci_name, cip->ci_min, cip->ci_max, cip->ci_default, confvar[cip->ci_index].cf_value, confvar[cip->ci_index].cf_notdefault); } } static void print_defaults(void) { logmsg(LOG_DEBUG, "Default interface variables:\n"); print_iflist(ifdefaults); logmsg(LOG_DEBUG, "Default prefix variables:\n"); print_prefixlist(prefixdefaults); } /* * Read from fp. Handle \ at the end of the line by joining lines together. * Return 0 on EOF. */ static int readline(FILE *fp, char *line, int length) { int got = 0; retry: errno = 0; if (fgets(line, length, fp) == NULL) { if (errno == EINTR) goto retry; if (got != 0) return (1); else return (0); } lineno++; got = strlen(line); /* Look for trailing \. Note that fgets includes the linefeed. */ if (got >= 2 && line[got-2] == '\\') { /* Skip \ and LF */ line += got - 2; length -= got - 2; goto retry; } /* Remove the trailing linefeed */ if (got > 0) line[got-1] = '\0'; return (1); } /* * Parse a line splitting it off at whitspace characters. * Modifies the content of the string by inserting NULLs. * If more arguments than fits in argvec/argcount then ignore the last. * Returns argcount. * Handles single quotes and double quotes. */ static int parse_line(char *line, char *argvec[], int argcount) { int i = 0; char *cp; boolean_t insingle_quote = _B_FALSE; boolean_t indouble_quote = _B_FALSE; /* Truncate at the beginning of a comment */ cp = strchr(line, '#'); if (cp != NULL) *cp = '\0'; for (;;) { /* Skip any whitespace */ while (isspace(*line) && *line != '\0') line++; if (*line == '\'') { line++; if (*line == '\0') return (i); insingle_quote = _B_TRUE; } else if (*line == '"') { line++; if (*line == '\0') return (i); indouble_quote = _B_TRUE; } argvec[i] = line; if (*line == '\0') return (i); i++; /* Skip until next whitespace or end of quoted text */ if (insingle_quote) { while (*line != '\'' && *line != '\0') line++; if (*line == '\'') { *line = ' '; } else { /* Handle missing quote at end */ i--; conferr("Missing end quote - ignoring <%s>\n", argvec[i]); return (i); } insingle_quote = _B_FALSE; } else if (indouble_quote) { while (*line != '"' && *line != '\0') line++; if (*line == '"') { *line = ' '; } else { /* Handle missing quote at end */ i--; conferr("Missing end quote - ignoring <%s>\n", argvec[i]); return (i); } indouble_quote = _B_FALSE; } else { while (!isspace(*line) && *line != '\0') line++; } if (*line != '\0') { /* Break off argument */ *line++ = '\0'; } if (i > argcount) return (argcount); } /* NOTREACHED */ } static void parse_var_value(config_type_t type, struct configinfo *list, char *varstr, char *valstr, struct confvar *confvar) { struct configinfo *cip; uint_t val; if (debug & D_CONFIG) { logmsg(LOG_DEBUG, "parse_var_value(%d, %s, %s)\n", (int)type, varstr, valstr); } for (cip = list; cip->ci_name != NULL; cip++) { if (strcasecmp(cip->ci_name, varstr) == 0) break; } if (cip->ci_name == NULL) { conferr("Unknown variable: <%s>\n", varstr); return; } if (!(*cip->ci_parsefunc)(valstr, &val)) { conferr("Bad value: <%s>\n", valstr); return; } if (cip->ci_min != 0 && val < cip->ci_min) { conferr("Value %s is below minimum %u for %s\n", valstr, cip->ci_min, varstr); return; } if (cip->ci_max != ~0U && val > cip->ci_max) { conferr("Value %s is above maximum %u for %s\n", valstr, cip->ci_max, varstr); return; } /* Check against dynamic/relative limits */ if (type == CONFIG_IF) { if (cip->ci_index == I_MinRtrAdvInterval && confvar[I_MaxRtrAdvInterval].cf_notdefault && val > confvar[I_MaxRtrAdvInterval].cf_value * 0.75) { conferr("MinRtrAdvInterval exceeds .75 * " "MaxRtrAdvInterval (%u)\n", confvar[I_MaxRtrAdvInterval].cf_value); return; } if (cip->ci_index == I_MaxRtrAdvInterval && confvar[I_MinRtrAdvInterval].cf_notdefault && confvar[I_MinRtrAdvInterval].cf_value > val * 0.75) { conferr("MinRtrAdvInterval (%u) exceeds .75 * " "MaxRtrAdvInterval\n", confvar[I_MinRtrAdvInterval].cf_value); return; } if (cip->ci_index == I_AdvDefaultLifetime && confvar[I_MaxRtrAdvInterval].cf_notdefault && val != 0 && val < confvar[I_MaxRtrAdvInterval].cf_value) { conferr("AdvDefaultLifetime is not between " "MaxRtrAdrInterval (%u) and 9000 seconds\n", confvar[I_MaxRtrAdvInterval].cf_value); return; } if (cip->ci_index == I_MaxRtrAdvInterval && confvar[I_AdvDefaultLifetime].cf_notdefault && confvar[I_AdvDefaultLifetime].cf_value < val) { conferr("AdvDefaultLifetime (%u) is not between " "MaxRtrAdrInterval and 9000 seconds\n", confvar[I_AdvDefaultLifetime].cf_value); return; } } confvar[cip->ci_index].cf_value = val; confvar[cip->ci_index].cf_notdefault = _B_TRUE; /* Derive dynamic/relative variables based on this one */ if (type == CONFIG_IF) { if (cip->ci_index == I_MaxRtrAdvInterval && !confvar[I_MinRtrAdvInterval].cf_notdefault) confvar[I_MinRtrAdvInterval].cf_value = val / 3; if (cip->ci_index == I_MaxRtrAdvInterval && !confvar[I_AdvDefaultLifetime].cf_notdefault) confvar[I_AdvDefaultLifetime].cf_value = 3 * val; } } /* * Split up the line into pairs */ static void parse_default(config_type_t type, struct configinfo *list, char *argvec[], int argcount, struct confvar *defaults) { if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_default: argc %d\n", argcount); while (argcount >= 2) { parse_var_value(type, list, argvec[0], argvec[1], defaults); argcount -= 2; argvec += 2; } if (argcount != 0) conferr("Trailing text <%s> ignored\n", argvec[0]); } /* * Returns true if ok; otherwise false. */ static void parse_if(struct configinfo *list, char *argvec[], int argcount) { char *ifname; struct phyint *pi; char save[sizeof (pi->pi_config)]; if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_if: argc %d\n", argcount); if (argcount < 1) { conferr("Missing interface name\n"); return; } ifname = argvec[0]; argvec++; argcount--; pi = phyint_lookup(ifname); if (pi == NULL) { /* * Create the physical interface structure. * Note, phyint_create() sets the interface * defaults in pi_config. */ pi = phyint_create(ifname); if (pi == NULL) { conferr("Unable to use interface %s\n", ifname); return; } } (void) memcpy(save, pi->pi_config, sizeof (save)); while (argcount >= 2) { parse_var_value(CONFIG_IF, list, argvec[0], argvec[1], pi->pi_config); argcount -= 2; argvec += 2; } if (argcount != 0) logmsg(LOG_ERR, "Trailing text <%s> ignored\n", argvec[0]); check_if_var_consistency(pi->pi_config, save, sizeof (save)); } static void parse_prefix(struct configinfo *list, char *argvec[], int argcount) { char *ifname, *prefix; struct phyint *pi; struct adv_prefix *adv_pr; struct in6_addr in6; int prefixlen; char save[sizeof (adv_pr->adv_pr_config)]; if (debug & D_CONFIG) logmsg(LOG_DEBUG, "parse_prefix: argc %d\n", argcount); if (argcount < 2) { conferr("Missing prefix and/or interface name\n"); return; } prefix = argvec[0]; ifname = argvec[1]; argvec += 2; argcount -= 2; prefixlen = parse_addrprefix(prefix, &in6); if (prefixlen == -1) { conferr("Bad prefix %s\n", prefix); return; } pi = phyint_lookup(ifname); if (pi == NULL) { /* * Create the physical interface structure. * Note, phyint_create() sets the interface * defaults in pi_config. */ pi = phyint_create(ifname); if (pi == NULL) { conferr("Unable to use interface %s\n", ifname); return; } } adv_pr = adv_prefix_lookup(pi, in6, prefixlen); if (adv_pr == NULL) { int i; adv_pr = adv_prefix_create(pi, in6, prefixlen); if (adv_pr == NULL) { conferr("Unable to create prefix %s\n", prefix); return; } /* * Copy the defaults from the default array. */ for (i = 0; i < I_PREFIXSIZE; i++) { adv_pr->adv_pr_config[i].cf_value = prefixdefaults[i].cf_value; adv_pr->adv_pr_config[i].cf_notdefault = prefixdefaults[i].cf_notdefault; } } (void) memcpy(save, adv_pr->adv_pr_config, sizeof (save)); while (argcount >= 2) { parse_var_value(CONFIG_PREFIX, list, argvec[0], argvec[1], adv_pr->adv_pr_config); argcount -= 2; argvec += 2; } check_var_consistency(adv_pr->adv_pr_config, save, sizeof (save)); if (argcount != 0) logmsg(LOG_ERR, "Trailing text <%s> ignored\n", argvec[0]); } /* * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_onoff(char *str, uint_t *resp) { if (strcasecmp(str, "on") == 0) { *resp = 1; return (_B_TRUE); } if (strcasecmp(str, "off") == 0) { *resp = 0; return (_B_TRUE); } if (strcasecmp(str, "true") == 0) { *resp = 1; return (_B_TRUE); } if (strcasecmp(str, "false") == 0) { *resp = 0; return (_B_TRUE); } if (parse_int(str, resp)) { if (*resp == 0 || *resp == 1) return (_B_TRUE); } return (_B_FALSE); } /* * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_int(char *str, uint_t *resp) { char *end; int res; res = strtoul(str, &end, 0); if (end == str) return (_B_FALSE); *resp = res; return (_B_TRUE); } /* * Parse something with a unit of millseconds. * Regognizes the suffixes "ms", "s", "m", "h", and "d". * * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_ms(char *str, uint_t *resp) { /* Look at the last and next to last character */ char *cp, *last, *nlast; char str2[BUFSIZ]; /* For local modification */ int multiplier = 1; (void) strncpy(str2, str, sizeof (str2)); str2[sizeof (str2) - 1] = '\0'; last = str2; nlast = NULL; for (cp = str2; *cp != '\0'; cp++) { nlast = last; last = cp; } if (debug & D_PARSE) { logmsg(LOG_DEBUG, "parse_ms: last <%c> nlast <%c>\n", (last != NULL ? *last : ' '), (nlast != NULL ? *nlast : ' ')); } switch (*last) { case 'd': multiplier *= 24; /* FALLTHRU */ case 'h': multiplier *= 60; /* FALLTHRU */ case 'm': multiplier *= 60; *last = '\0'; multiplier *= 1000; /* Convert to milliseconds */ break; case 's': /* Could be "ms" or "s" */ if (nlast != NULL && *nlast == 'm') { /* "ms" */ *nlast = '\0'; } else { *last = '\0'; multiplier *= 1000; /* Convert to milliseconds */ } break; } if (!parse_int(str2, resp)) return (_B_FALSE); *resp *= multiplier; return (_B_TRUE); } /* * Parse something with a unit of seconds. * Regognizes the suffixes "s", "m", "h", and "d". * * Returns true if ok (and *resp updated) and false if failed. */ static boolean_t parse_s(char *str, uint_t *resp) { /* Look at the last character */ char *cp, *last; char str2[BUFSIZ]; /* For local modification */ int multiplier = 1; (void) strncpy(str2, str, sizeof (str2)); str2[sizeof (str2) - 1] = '\0'; last = str2; for (cp = str2; *cp != '\0'; cp++) { last = cp; } if (debug & D_PARSE) { logmsg(LOG_DEBUG, "parse_s: last <%c>\n", (last != NULL ? *last : ' ')); } switch (*last) { case 'd': multiplier *= 24; /* FALLTHRU */ case 'h': multiplier *= 60; /* FALLTHRU */ case 'm': multiplier *= 60; /* FALLTHRU */ case 's': *last = '\0'; break; } if (!parse_int(str2, resp)) return (_B_FALSE); *resp *= multiplier; return (_B_TRUE); } /* * Return prefixlen (0 to 128) if ok; -1 if failed. */ static int parse_addrprefix(char *strin, struct in6_addr *in6) { char str[BUFSIZ]; /* Local copy for modification */ int prefixlen; char *cp; char *end; (void) strncpy(str, strin, sizeof (str)); str[sizeof (str) - 1] = '\0'; cp = strchr(str, '/'); if (cp == NULL) return (-1); *cp = '\0'; cp++; prefixlen = strtol(cp, &end, 10); if (cp == end) return (-1); if (prefixlen < 0 || prefixlen > IPV6_ABITS) return (-1); if (inet_pton(AF_INET6, str, in6) != 1) return (-1); return (prefixlen); } /* * Parse an absolute date using a datemsk config file. * Return the difference (measured in seconds) between that date/time and * the current date/time. * If the date has passed return zero. * * Returns true if ok (and *resp updated) and false if failed. * XXX Due to getdate limitations can not exceed year 2038. */ static boolean_t parse_date(char *str, uint_t *resp) { struct tm *tm; struct timeval tvs; time_t time, ntime; if (getenv("DATEMSK") == NULL) { (void) putenv("DATEMSK=/etc/inet/datemsk.ndpd"); } if (gettimeofday(&tvs, NULL) < 0) { logperror("gettimeofday"); return (_B_FALSE); } time = tvs.tv_sec; tm = getdate(str); if (tm == NULL) { logmsg(LOG_ERR, "Bad date <%s> (error %d)\n", str, getdate_err); return (_B_FALSE); } ntime = mktime(tm); if (debug & D_PARSE) { char buf[BUFSIZ]; (void) strftime(buf, sizeof (buf), "%Y-%m-%d %R %Z", tm); logmsg(LOG_DEBUG, "parse_date: <%s>, delta %ld seconds\n", buf, ntime - time); } if (ntime < time) { conferr("Date in the past <%s>\n", str); *resp = 0; return (_B_TRUE); } *resp = (ntime - time); return (_B_TRUE); } /* PRINTFLIKE1 */ static void conferr(char *fmt, ...) { char msg[NDPD_LOGMSGSIZE]; size_t slen; va_list ap; va_start(ap, fmt); (void) snprintf(msg, NDPD_LOGMSGSIZE, "%s line %d: ", conf_filename, lineno); slen = strlen(msg); (void) vsnprintf(msg + slen, NDPD_LOGMSGSIZE - slen, fmt, ap); logmsg(LOG_ERR, "%s", msg); va_end(ap); } static FILE * open_conffile(char *filename) { if (strlcpy(conf_filename, filename, MAXPATHLEN) >= MAXPATHLEN) { logmsg(LOG_ERR, "config file pathname is too long\n"); return (NULL); } lineno = 0; return (fopen(filename, "r")); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NDPD_DEFS_H #define _NDPD_DEFS_H #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 #include #include #include #include #include #include "tables.h" #ifdef __cplusplus extern "C" { #endif #define CURHOP_UNSPECIFIED 0 #define PATH_NDPD_CONF "/etc/inet/ndpd.conf" extern int debug, no_loopback; extern struct in6_addr all_nodes_mcast; extern struct in6_addr all_routers_mcast; extern int rtsock; extern struct rt_msghdr *rt_msg; extern struct sockaddr_in6 *rta_gateway; extern struct sockaddr_dl *rta_ifp; /* Debug flags */ #define D_ALL 0xffff #define D_DEFAULTS 0x0001 /* Default values in config file */ #define D_CONFIG 0x0002 /* Config file */ #define D_PHYINT 0x0004 /* phyint table */ #define D_PREFIX 0x0008 /* prefix table */ #define D_ROUTER 0x0010 /* router table */ #define D_STATE 0x0020 /* RS/RA state machine */ #define D_IFSCAN 0x0040 /* Scan of kernel interfaces */ #define D_TIMER 0x0080 /* Timer mechanism */ #define D_PARSE 0x0100 /* config file parser */ #define D_PKTIN 0x0200 /* Received packet */ #define D_PKTBAD 0x0400 /* Malformed packet */ #define D_PKTOUT 0x0800 /* Sent packet */ #define D_TMP 0x1000 /* RFC3041 mechanism */ #define D_DHCP 0x2000 /* RFC3315 DHCPv6 (stateful addrs) */ #define IF_SEPARATOR ':' #define IPV6_MAX_HOPS 255 #define IPV6_MIN_MTU (1024+256) #define IPV6_ABITS 128 #define TMP_TOKEN_BITS 64 #define TMP_TOKEN_BYTES (TMP_TOKEN_BITS / 8) #define MAX_DAD_FAILURES 5 /* Return a random number from a an range inclusive of the endpoints */ #define GET_RANDOM(LOW, HIGH) (random() % ((HIGH) - (LOW) + 1) + (LOW)) #define TIMER_INFINITY 0xFFFFFFFFU /* Never time out */ #define PREFIX_INFINITY 0XFFFFFFFFU /* A "forever" prefix lifetime */ /* * Used by 2 hour rule for stateless addrconf */ #define MIN_VALID_LIFETIME (2*60*60) /* In seconds */ /* * Control how often pi_ReachableTime gets re-randomized */ #define MIN_REACH_RANDOM_INTERVAL (60*1000) /* 1 minute in ms */ #define MAX_REACH_RANDOM_INTERVAL (60*60*1000) /* 1 hour in ms */ /* * Parsing constants */ #define MAXLINELEN 4096 #define MAXARGSPERLINE 128 void timer_schedule(uint_t delay); extern void logmsg(int level, const char *fmt, ...); extern void logperror(const char *str); extern void logperror_pi(const struct phyint *pi, const char *str); extern void logperror_pr(const struct prefix *pr, const char *str); extern int parse_config(char *config_file, boolean_t file_required); extern int poll_add(int fd); extern int poll_remove(int fd); extern char *fmt_lla(char *llabuf, int bufsize, uchar_t *lla, int llalen); extern int do_dad(char *ifname, struct sockaddr_in6 *testaddr); #ifdef __cplusplus } #endif #endif /* _NDPD_DEFS_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 (c) 1999, 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2024 Oxide Computer Company */ #include "defs.h" #include "tables.h" #include #include static void initlog(void); static void run_timeouts(void); static void advertise(struct sockaddr_in6 *sin6, struct phyint *pi, boolean_t no_prefixes); static void solicit(struct sockaddr_in6 *sin6, struct phyint *pi); static void initifs(boolean_t first); static void check_if_removed(struct phyint *pi); static void loopback_ra_enqueue(struct phyint *pi, struct nd_router_advert *ra, int len); static void loopback_ra_dequeue(void); static void check_daemonize(void); struct in6_addr all_nodes_mcast = { { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 } }; struct in6_addr all_routers_mcast = { { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2 } }; static struct sockaddr_in6 v6allnodes = { AF_INET6, 0, 0, { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x1 } }; static struct sockaddr_in6 v6allrouters = { AF_INET6, 0, 0, { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x2 } }; static char **argv0; /* Saved for re-exec on SIGHUP */ static uint64_t packet[(IP_MAXPACKET + 1)/8]; static int show_ifs = 0; static boolean_t already_daemonized = _B_FALSE; int debug = 0; int no_loopback = 0; /* Do not send RA packets to ourselves */ /* * Size of routing socket message used by in.ndpd which includes the header, * space for the RTA_DST, RTA_GATEWAY and RTA_NETMASK (each a sockaddr_in6) * plus space for the RTA_IFP (a sockaddr_dl). */ #define NDP_RTM_MSGLEN sizeof (struct rt_msghdr) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_dl) /* * These are referenced externally in tables.c in order to fill in the * dynamic portions of the routing socket message and then to send the message * itself. */ int rtsock = -1; /* Routing socket */ struct rt_msghdr *rt_msg; /* Routing socket message */ struct sockaddr_in6 *rta_gateway; /* RTA_GATEWAY sockaddr */ struct sockaddr_dl *rta_ifp; /* RTA_IFP sockaddr */ /* * These sockets are used internally in this file. */ static int mibsock = -1; /* mib request socket */ static int cmdsock = -1; /* command socket */ static int ndpd_setup_cmd_listener(void); static void ndpd_cmd_handler(int); static int ndpd_process_cmd(int, ipadm_ndpd_msg_t *); static int ndpd_send_error(int, int); static int ndpd_set_autoconf(const char *, boolean_t); static int ndpd_create_addrs(const char *, struct sockaddr_in6, int, boolean_t, boolean_t, char *); static int ndpd_delete_addrs(const char *); static int phyint_check_ipadm_intfid(struct phyint *); /* * Return the current time in milliseconds truncated to * fit in an integer. */ uint_t getcurrenttime(void) { struct timeval tp; if (gettimeofday(&tp, NULL) < 0) { logperror("getcurrenttime: gettimeofday failed"); exit(1); } return (tp.tv_sec * 1000 + tp.tv_usec / 1000); } /* * Output a preformated packet from the packet[] buffer. */ static void sendpacket(struct sockaddr_in6 *sin6, int sock, int size, int flags) { int cc; char abuf[INET6_ADDRSTRLEN]; cc = sendto(sock, (char *)packet, size, flags, (struct sockaddr *)sin6, sizeof (*sin6)); if (cc < 0 || cc != size) { if (cc < 0) { logperror("sendpacket: sendto"); } logmsg(LOG_ERR, "sendpacket: wrote %s %d chars, ret=%d\n", inet_ntop(sin6->sin6_family, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), size, cc); } } /* * If possible, place an ND_OPT_SOURCE_LINKADDR option at `optp'. * Return the number of bytes placed in the option. */ static uint_t add_opt_lla(struct phyint *pi, struct nd_opt_lla *optp) { uint_t optlen; uint_t hwaddrlen; struct lifreq lifr; /* If this phyint doesn't have a link-layer address, bail */ if (phyint_get_lla(pi, &lifr) == -1) return (0); hwaddrlen = lifr.lifr_nd.lnr_hdw_len; /* roundup to multiple of 8 and make padding zero */ optlen = ((sizeof (struct nd_opt_hdr) + hwaddrlen + 7) / 8) * 8; bzero(optp, optlen); optp->nd_opt_lla_type = ND_OPT_SOURCE_LINKADDR; optp->nd_opt_lla_len = optlen / 8; bcopy(lifr.lifr_nd.lnr_hdw_addr, optp->nd_opt_lla_hdw_addr, hwaddrlen); return (optlen); } /* Send a Router Solicitation */ static void solicit(struct sockaddr_in6 *sin6, struct phyint *pi) { int packetlen = 0; struct nd_router_solicit *rs = (struct nd_router_solicit *)packet; char *pptr = (char *)packet; rs->nd_rs_type = ND_ROUTER_SOLICIT; rs->nd_rs_code = 0; rs->nd_rs_cksum = htons(0); rs->nd_rs_reserved = htonl(0); packetlen += sizeof (*rs); pptr += sizeof (*rs); /* add options */ packetlen += add_opt_lla(pi, (struct nd_opt_lla *)pptr); if (debug & D_PKTOUT) { print_route_sol("Sending solicitation to ", pi, rs, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); } /* * Send a (set of) Router Advertisements and feed them back to ourselves * for processing. Unless no_prefixes is set all prefixes are included. * If there are too many prefix options to fit in one packet multiple * packets will be sent - each containing a subset of the prefix options. */ static void advertise(struct sockaddr_in6 *sin6, struct phyint *pi, boolean_t no_prefixes) { struct nd_opt_prefix_info *po; char *pptr = (char *)packet; struct nd_router_advert *ra; struct adv_prefix *adv_pr; int packetlen = 0; ra = (struct nd_router_advert *)pptr; ra->nd_ra_type = ND_ROUTER_ADVERT; ra->nd_ra_code = 0; ra->nd_ra_cksum = htons(0); ra->nd_ra_curhoplimit = pi->pi_AdvCurHopLimit; ra->nd_ra_flags_reserved = 0; if (pi->pi_AdvManagedFlag) ra->nd_ra_flags_reserved |= ND_RA_FLAG_MANAGED; if (pi->pi_AdvOtherConfigFlag) ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; if (pi->pi_adv_state == FINAL_ADV) ra->nd_ra_router_lifetime = htons(0); else ra->nd_ra_router_lifetime = htons(pi->pi_AdvDefaultLifetime); ra->nd_ra_reachable = htonl(pi->pi_AdvReachableTime); ra->nd_ra_retransmit = htonl(pi->pi_AdvRetransTimer); packetlen = sizeof (*ra); pptr += sizeof (*ra); if (pi->pi_adv_state == FINAL_ADV) { if (debug & D_PKTOUT) { print_route_adv("Sending advert (FINAL) to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); return; } /* add options */ packetlen += add_opt_lla(pi, (struct nd_opt_lla *)pptr); pptr = (char *)packet + packetlen; if (pi->pi_AdvLinkMTU != 0) { struct nd_opt_mtu *mo = (struct nd_opt_mtu *)pptr; mo->nd_opt_mtu_type = ND_OPT_MTU; mo->nd_opt_mtu_len = sizeof (struct nd_opt_mtu) / 8; mo->nd_opt_mtu_reserved = 0; mo->nd_opt_mtu_mtu = htonl(pi->pi_AdvLinkMTU); packetlen += sizeof (struct nd_opt_mtu); pptr += sizeof (struct nd_opt_mtu); } if (no_prefixes) { if (debug & D_PKTOUT) { print_route_adv("Sending advert to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); return; } po = (struct nd_opt_prefix_info *)pptr; for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { if (!adv_pr->adv_pr_AdvOnLinkFlag && !adv_pr->adv_pr_AdvAutonomousFlag) { continue; } /* * If the prefix doesn't fit in packet send * what we have so far and start with new packet. */ if (packetlen + sizeof (*po) > pi->pi_LinkMTU - sizeof (struct ip6_hdr)) { if (debug & D_PKTOUT) { print_route_adv("Sending advert " "(FRAG) to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); packetlen = sizeof (*ra); pptr = (char *)packet + sizeof (*ra); po = (struct nd_opt_prefix_info *)pptr; } po->nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; po->nd_opt_pi_len = sizeof (*po)/8; po->nd_opt_pi_flags_reserved = 0; if (adv_pr->adv_pr_AdvOnLinkFlag) { po->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_ONLINK; } if (adv_pr->adv_pr_AdvAutonomousFlag) { po->nd_opt_pi_flags_reserved |= ND_OPT_PI_FLAG_AUTO; } po->nd_opt_pi_prefix_len = adv_pr->adv_pr_prefix_len; /* * If both Adv*Expiration and Adv*Lifetime are * set we prefer the former and make the lifetime * decrement in real time. */ if (adv_pr->adv_pr_AdvValidRealTime) { po->nd_opt_pi_valid_time = htonl(adv_pr->adv_pr_AdvValidExpiration); } else { po->nd_opt_pi_valid_time = htonl(adv_pr->adv_pr_AdvValidLifetime); } if (adv_pr->adv_pr_AdvPreferredRealTime) { po->nd_opt_pi_preferred_time = htonl(adv_pr->adv_pr_AdvPreferredExpiration); } else { po->nd_opt_pi_preferred_time = htonl(adv_pr->adv_pr_AdvPreferredLifetime); } po->nd_opt_pi_reserved2 = htonl(0); po->nd_opt_pi_prefix = adv_pr->adv_pr_prefix; po++; packetlen += sizeof (*po); } if (debug & D_PKTOUT) { print_route_adv("Sending advert to ", pi, ra, packetlen, sin6); } sendpacket(sin6, pi->pi_sock, packetlen, 0); /* Feed packet back in for router operation */ loopback_ra_enqueue(pi, ra, packetlen); } /* Poll support */ static int pollfd_num = 0; /* Allocated and initialized */ static struct pollfd *pollfds = NULL; /* * Add fd to the set being polled. Returns 0 if ok; -1 if failed. */ int poll_add(int fd) { int i; int new_num; struct pollfd *newfds; /* Check if already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == fd) return (0); } /* Check for empty spot already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == -1) { pollfds[i].fd = fd; return (0); } } /* Allocate space for 32 more fds and initialize to -1 */ new_num = pollfd_num + 32; newfds = realloc(pollfds, new_num * sizeof (struct pollfd)); if (newfds == NULL) { logperror("realloc"); return (-1); } newfds[pollfd_num].fd = fd; newfds[pollfd_num++].events = POLLIN; for (i = pollfd_num; i < new_num; i++) { newfds[i].fd = -1; newfds[i].events = POLLIN; } pollfd_num = new_num; pollfds = newfds; return (0); } /* * Remove fd from the set being polled. Returns 0 if ok; -1 if failed. */ int poll_remove(int fd) { int i; /* Check if already present */ for (i = 0; i < pollfd_num; i++) { if (pollfds[i].fd == fd) { pollfds[i].fd = -1; return (0); } } return (-1); } /* * Extract information about the ifname (either a physical interface and * the ":0" logical interface or just a logical interface). * If the interface (still) exists in kernel set pr_in_use * for caller to be able to detect interfaces that are removed. * Starts sending advertisements/solicitations when new physical interfaces * are detected. */ static void if_process(int s, char *ifname, boolean_t first) { struct lifreq lifr; struct phyint *pi; struct prefix *pr; char *cp; char phyintname[LIFNAMSIZ + 1]; if (debug & D_IFSCAN) logmsg(LOG_DEBUG, "if_process(%s)\n", ifname); (void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(s, SIOCGLIFFLAGS, (char *)&lifr) < 0) { if (errno == ENXIO) { /* * Interface has disappeared */ return; } logperror("if_process: ioctl (get interface flags)"); return; } /* * Ignore loopback, point-to-multipoint and VRRP interfaces. * The IP addresses over VRRP interfaces cannot be auto-configured. * Point-to-point interfaces always have IFF_MULTICAST set. */ if (!(lifr.lifr_flags & IFF_MULTICAST) || (lifr.lifr_flags & (IFF_LOOPBACK|IFF_VRRP))) { return; } if (!(lifr.lifr_flags & IFF_IPV6)) return; (void) strncpy(phyintname, ifname, sizeof (phyintname)); phyintname[sizeof (phyintname) - 1] = '\0'; if ((cp = strchr(phyintname, IF_SEPARATOR)) != NULL) { *cp = '\0'; } pi = phyint_lookup(phyintname); if (pi == NULL) { pi = phyint_create(phyintname); if (pi == NULL) { logmsg(LOG_ERR, "if_process: out of memory\n"); return; } } else { /* * if the phyint already exists, synchronize it with * the kernel state. For a newly created phyint, phyint_create * calls phyint_init_from_k(). */ (void) phyint_init_from_k(pi); } /* * Immediately after restart, check with ipmgmtd if there is * any interface id to be configured for this interface. If * interface configuration is still in progress as we're * starting, this will clear pi->pi_autoconf so we don't get * ahead of ourselves; ipadm will poke us later to turn it * back on to restart configuration. */ if (first) { if (phyint_check_ipadm_intfid(pi) == -1) logmsg(LOG_ERR, "Could not get ipadm info\n"); } if (pi->pi_sock == -1 && !(pi->pi_kernel_state & PI_PRESENT)) { /* Interface is not yet present */ if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "if_process: interface not yet " "present %s\n", pi->pi_name); } return; } if (pi->pi_sock != -1) { if (poll_add(pi->pi_sock) == -1) { /* * reset state. */ phyint_cleanup(pi); } } /* * Check if IFF_ROUTER has been turned off in kernel in which * case we have to turn off AdvSendAdvertisements. * The kernel will automatically turn off IFF_ROUTER if * ip6_forwarding is turned off. * Note that we do not switch back should IFF_ROUTER be turned on. */ if (!first && pi->pi_AdvSendAdvertisements && !(pi->pi_flags & IFF_ROUTER)) { logmsg(LOG_INFO, "No longer a router on %s\n", pi->pi_name); check_to_advertise(pi, START_FINAL_ADV); pi->pi_AdvSendAdvertisements = 0; pi->pi_sol_state = NO_SOLICIT; } /* * Send advertisments and solicitation only if the interface is * present in the kernel. */ if (pi->pi_kernel_state & PI_PRESENT) { if (pi->pi_AdvSendAdvertisements) { if (pi->pi_adv_state == NO_ADV) check_to_advertise(pi, START_INIT_ADV); } else { if (pi->pi_sol_state == NO_SOLICIT) check_to_solicit(pi, START_INIT_SOLICIT); } } /* * Track static kernel prefixes to prevent in.ndpd from clobbering * them by creating a struct prefix for each prefix detected in the * kernel. */ pr = prefix_lookup_name(pi, ifname); if (pr == NULL) { pr = prefix_create_name(pi, ifname); if (pr == NULL) { logmsg(LOG_ERR, "if_process: out of memory\n"); return; } if (prefix_init_from_k(pr) == -1) { prefix_delete(pr); return; } } /* Detect prefixes which are removed */ if (pr->pr_kernel_state != 0) pr->pr_in_use = _B_TRUE; if ((lifr.lifr_flags & IFF_DUPLICATE) && !(lifr.lifr_flags & IFF_DHCPRUNNING) && (pr->pr_flags & IFF_TEMPORARY)) { in6_addr_t *token; int i; char abuf[INET6_ADDRSTRLEN]; if (++pr->pr_attempts >= MAX_DAD_FAILURES) { logmsg(LOG_ERR, "%s: token %s is duplicate after %d " "attempts; disabling temporary addresses on %s", pr->pr_name, inet_ntop(AF_INET6, (void *)&pi->pi_tmp_token, abuf, sizeof (abuf)), pr->pr_attempts, pi->pi_name); pi->pi_TmpAddrsEnabled = 0; tmptoken_delete(pi); prefix_delete(pr); return; } logmsg(LOG_WARNING, "%s: token %s is duplicate; trying again", pr->pr_name, inet_ntop(AF_INET6, (void *)&pi->pi_tmp_token, abuf, sizeof (abuf))); if (!tmptoken_create(pi)) { prefix_delete(pr); return; } token = &pi->pi_tmp_token; for (i = 0; i < 16; i++) { /* * prefix_create ensures that pr_prefix has all-zero * bits after prefixlen. */ pr->pr_address.s6_addr[i] = pr->pr_prefix.s6_addr[i] | token->s6_addr[i]; } if (prefix_lookup_addr_match(pr) != NULL) { prefix_delete(pr); return; } pr->pr_CreateTime = getcurrenttime() / MILLISEC; /* * We've got a new token. Clearing PR_AUTO causes * prefix_update_k to bring the interface up and set the * address. */ pr->pr_kernel_state &= ~PR_AUTO; prefix_update_k(pr); } } static int ifsock = -1; /* * Scan all interfaces to detect changes as well as new and deleted intefaces * 'first' is set for the initial call only. Do not effect anything. */ static void initifs(boolean_t first) { char *buf; int bufsize; int numifs; int n; struct lifnum lifn; struct lifconf lifc; struct lifreq *lifr; struct phyint *pi; struct phyint *next_pi; struct prefix *pr; if (debug & D_IFSCAN) logmsg(LOG_DEBUG, "Reading interface configuration\n"); if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { logperror("initifs: socket"); return; } } lifn.lifn_family = AF_INET6; lifn.lifn_flags = LIFC_NOXMIT | LIFC_TEMPORARY; if (ioctl(ifsock, SIOCGLIFNUM, (char *)&lifn) < 0) { logperror("initifs: ioctl (get interface numbers)"); return; } numifs = lifn.lifn_count; bufsize = numifs * sizeof (struct lifreq); buf = (char *)malloc(bufsize); if (buf == NULL) { logmsg(LOG_ERR, "initifs: out of memory\n"); return; } /* * Mark the interfaces so that we can find phyints and prefixes * which have disappeared from the kernel. * if_process will set pr_in_use when it finds the interface * in the kernel. */ for (pi = phyints; pi != NULL; pi = pi->pi_next) { /* * Before re-examining the state of the interfaces, * PI_PRESENT should be cleared from pi_kernel_state. */ pi->pi_kernel_state &= ~PI_PRESENT; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { pr->pr_in_use = _B_FALSE; } } lifc.lifc_family = AF_INET6; lifc.lifc_flags = LIFC_NOXMIT | LIFC_TEMPORARY; lifc.lifc_len = bufsize; lifc.lifc_buf = buf; if (ioctl(ifsock, SIOCGLIFCONF, (char *)&lifc) < 0) { logperror("initifs: ioctl (get interface configuration)"); free(buf); return; } lifr = (struct lifreq *)lifc.lifc_req; for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifr++) if_process(ifsock, lifr->lifr_name, first); free(buf); /* * Detect phyints that have been removed from the kernel. * Since we can't recreate it here (would require ifconfig plumb * logic) we just terminate use of that phyint. */ for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; /* * If interface (still) exists in kernel, set * pi_state to indicate that. */ if (pi->pi_kernel_state & PI_PRESENT) { pi->pi_state |= PI_PRESENT; } check_if_removed(pi); } if (show_ifs) phyint_print_all(); } /* * Router advertisement state machine. Used for everything but timer * events which use advertise_event directly. */ void check_to_advertise(struct phyint *pi, enum adv_events event) { uint_t delay; enum adv_states old_state = pi->pi_adv_state; if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_advertise(%s, %d) state %d\n", pi->pi_name, (int)event, (int)old_state); } delay = advertise_event(pi, event, 0); if (delay != TIMER_INFINITY) { /* Make sure the global next event is updated */ timer_schedule(delay); } if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_advertise(%s, %d) state %d -> %d\n", pi->pi_name, (int)event, (int)old_state, (int)pi->pi_adv_state); } } /* * Router advertisement state machine. * Return the number of milliseconds until next timeout (TIMER_INFINITY * if never). * For the ADV_TIMER event the caller passes in the number of milliseconds * since the last timer event in the 'elapsed' parameter. */ uint_t advertise_event(struct phyint *pi, enum adv_events event, uint_t elapsed) { uint_t delay; if (debug & D_STATE) { logmsg(LOG_DEBUG, "advertise_event(%s, %d, %d) state %d\n", pi->pi_name, (int)event, elapsed, (int)pi->pi_adv_state); } check_daemonize(); if (!pi->pi_AdvSendAdvertisements) return (TIMER_INFINITY); if (pi->pi_flags & IFF_NORTEXCH) { if (debug & D_PKTOUT) { logmsg(LOG_DEBUG, "Suppress sending RA packet on %s " "(no route exchange on interface)\n", pi->pi_name); } return (TIMER_INFINITY); } switch (event) { case ADV_OFF: pi->pi_adv_state = NO_ADV; return (TIMER_INFINITY); case START_INIT_ADV: if (pi->pi_adv_state == INIT_ADV) return (pi->pi_adv_time_left); pi->pi_adv_count = ND_MAX_INITIAL_RTR_ADVERTISEMENTS; pi->pi_adv_time_left = 0; pi->pi_adv_state = INIT_ADV; break; /* send advertisement */ case START_FINAL_ADV: if (pi->pi_adv_state == NO_ADV) return (TIMER_INFINITY); if (pi->pi_adv_state == FINAL_ADV) return (pi->pi_adv_time_left); pi->pi_adv_count = ND_MAX_FINAL_RTR_ADVERTISEMENTS; pi->pi_adv_time_left = 0; pi->pi_adv_state = FINAL_ADV; break; /* send advertisement */ case RECEIVED_SOLICIT: if (pi->pi_adv_state == NO_ADV) return (TIMER_INFINITY); if (pi->pi_adv_state == SOLICIT_ADV) { if (pi->pi_adv_time_left != 0) return (pi->pi_adv_time_left); break; } delay = GET_RANDOM(0, ND_MAX_RA_DELAY_TIME); if (delay < pi->pi_adv_time_left) pi->pi_adv_time_left = delay; if (pi->pi_adv_time_since_sent < ND_MIN_DELAY_BETWEEN_RAS) { /* * Send an advertisement (ND_MIN_DELAY_BETWEEN_RAS * plus random delay) after the previous * advertisement was sent. */ pi->pi_adv_time_left = delay + ND_MIN_DELAY_BETWEEN_RAS - pi->pi_adv_time_since_sent; } pi->pi_adv_state = SOLICIT_ADV; break; case ADV_TIMER: if (pi->pi_adv_state == NO_ADV) return (TIMER_INFINITY); /* Decrease time left */ if (pi->pi_adv_time_left >= elapsed) pi->pi_adv_time_left -= elapsed; else pi->pi_adv_time_left = 0; /* Increase time since last advertisement was sent */ pi->pi_adv_time_since_sent += elapsed; break; default: logmsg(LOG_ERR, "advertise_event: Unknown event %d\n", (int)event); return (TIMER_INFINITY); } if (pi->pi_adv_time_left != 0) return (pi->pi_adv_time_left); /* Send advertisement and calculate next time to send */ if (pi->pi_adv_state == FINAL_ADV) { /* Omit the prefixes */ advertise(&v6allnodes, pi, _B_TRUE); } else { advertise(&v6allnodes, pi, _B_FALSE); } pi->pi_adv_time_since_sent = 0; switch (pi->pi_adv_state) { case SOLICIT_ADV: /* * The solicited advertisement has been sent. * Revert to periodic advertisements. */ pi->pi_adv_state = REG_ADV; /* FALLTHRU */ case REG_ADV: pi->pi_adv_time_left = GET_RANDOM(1000 * pi->pi_MinRtrAdvInterval, 1000 * pi->pi_MaxRtrAdvInterval); break; case INIT_ADV: if (--pi->pi_adv_count > 0) { delay = GET_RANDOM(1000 * pi->pi_MinRtrAdvInterval, 1000 * pi->pi_MaxRtrAdvInterval); if (delay > ND_MAX_INITIAL_RTR_ADVERT_INTERVAL) delay = ND_MAX_INITIAL_RTR_ADVERT_INTERVAL; pi->pi_adv_time_left = delay; } else { pi->pi_adv_time_left = GET_RANDOM(1000 * pi->pi_MinRtrAdvInterval, 1000 * pi->pi_MaxRtrAdvInterval); pi->pi_adv_state = REG_ADV; } break; case FINAL_ADV: if (--pi->pi_adv_count > 0) { pi->pi_adv_time_left = ND_MAX_INITIAL_RTR_ADVERT_INTERVAL; } else { pi->pi_adv_state = NO_ADV; } break; } if (pi->pi_adv_state != NO_ADV) return (pi->pi_adv_time_left); else return (TIMER_INFINITY); } /* * Router solicitation state machine. Used for everything but timer * events which use solicit_event directly. */ void check_to_solicit(struct phyint *pi, enum solicit_events event) { uint_t delay; enum solicit_states old_state = pi->pi_sol_state; if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_solicit(%s, %d) state %d\n", pi->pi_name, (int)event, (int)old_state); } delay = solicit_event(pi, event, 0); if (delay != TIMER_INFINITY) { /* Make sure the global next event is updated */ timer_schedule(delay); } if (debug & D_STATE) { logmsg(LOG_DEBUG, "check_to_solicit(%s, %d) state %d -> %d\n", pi->pi_name, (int)event, (int)old_state, (int)pi->pi_sol_state); } } static void daemonize_ndpd(void) { struct itimerval it; boolean_t timerval = _B_TRUE; /* * Need to get current timer settings so they can be restored * after the fork(), as the it_value and it_interval values for * the ITIMER_REAL timer are reset to 0 in the child process. */ if (getitimer(ITIMER_REAL, &it) < 0) { if (debug & D_TIMER) logmsg(LOG_DEBUG, "daemonize_ndpd: failed to get itimerval\n"); timerval = _B_FALSE; } /* Daemonize. */ if (daemon(0, 0) == -1) { logperror("fork"); exit(1); } already_daemonized = _B_TRUE; /* * Restore timer values, if we were able to save them; if not, * check and set the right value by calling run_timeouts(). */ if (timerval) { if (setitimer(ITIMER_REAL, &it, NULL) < 0) { logperror("daemonize_ndpd: setitimer"); exit(2); } } else { run_timeouts(); } } /* * Check to see if the time is right to daemonize. The right time is when: * * 1. We haven't already daemonized. * 2. We are not in debug mode. * 3. All interfaces are marked IFF_NOXMIT. * 4. All non-router interfaces have their prefixes set up and we're * done sending router solicitations on those interfaces without * prefixes. */ static void check_daemonize(void) { struct phyint *pi; if (already_daemonized || debug != 0) return; for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (!(pi->pi_flags & IFF_NOXMIT)) break; } /* * If we can't transmit on any of the interfaces there is no reason * to hold up progress. */ if (pi == NULL) { daemonize_ndpd(); return; } /* Check all interfaces. If any are still soliciting, just return. */ for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pi->pi_AdvSendAdvertisements || !(pi->pi_kernel_state & PI_PRESENT)) continue; if (pi->pi_sol_state == INIT_SOLICIT) return; } daemonize_ndpd(); } /* * Router solicitation state machine. * Return the number of milliseconds until next timeout (TIMER_INFINITY * if never). * For the SOL_TIMER event the caller passes in the number of milliseconds * since the last timer event in the 'elapsed' parameter. */ uint_t solicit_event(struct phyint *pi, enum solicit_events event, uint_t elapsed) { if (debug & D_STATE) { logmsg(LOG_DEBUG, "solicit_event(%s, %d, %d) state %d\n", pi->pi_name, (int)event, elapsed, (int)pi->pi_sol_state); } if (pi->pi_AdvSendAdvertisements) return (TIMER_INFINITY); if (pi->pi_flags & IFF_NORTEXCH) { if (debug & D_PKTOUT) { logmsg(LOG_DEBUG, "Suppress sending RS packet on %s " "(no route exchange on interface)\n", pi->pi_name); } return (TIMER_INFINITY); } switch (event) { case SOLICIT_OFF: pi->pi_sol_state = NO_SOLICIT; check_daemonize(); return (TIMER_INFINITY); case SOLICIT_DONE: pi->pi_sol_state = DONE_SOLICIT; check_daemonize(); return (TIMER_INFINITY); case RESTART_INIT_SOLICIT: /* * This event allows us to start solicitation over again * without losing the RA flags. We start solicitation over * when we are missing an interface prefix for a newly- * encountered DHCP interface. */ if (pi->pi_sol_state == INIT_SOLICIT) return (pi->pi_sol_time_left); pi->pi_sol_count = ND_MAX_RTR_SOLICITATIONS; pi->pi_sol_time_left = GET_RANDOM(0, ND_MAX_RTR_SOLICITATION_DELAY); pi->pi_sol_state = INIT_SOLICIT; break; case START_INIT_SOLICIT: if (pi->pi_sol_state == INIT_SOLICIT) return (pi->pi_sol_time_left); pi->pi_ra_flags = 0; pi->pi_sol_count = ND_MAX_RTR_SOLICITATIONS; pi->pi_sol_time_left = GET_RANDOM(0, ND_MAX_RTR_SOLICITATION_DELAY); pi->pi_sol_state = INIT_SOLICIT; break; case SOL_TIMER: if (pi->pi_sol_state == NO_SOLICIT) return (TIMER_INFINITY); /* Decrease time left */ if (pi->pi_sol_time_left >= elapsed) pi->pi_sol_time_left -= elapsed; else pi->pi_sol_time_left = 0; break; default: logmsg(LOG_ERR, "solicit_event: Unknown event %d\n", (int)event); return (TIMER_INFINITY); } if (pi->pi_sol_time_left != 0) return (pi->pi_sol_time_left); /* Send solicitation and calculate next time */ switch (pi->pi_sol_state) { case INIT_SOLICIT: solicit(&v6allrouters, pi); if (--pi->pi_sol_count == 0) { if (debug & D_STATE) { logmsg(LOG_DEBUG, "solicit_event: no routers " "found on %s; assuming default flags\n", pi->pi_name); } if (pi->pi_autoconf && pi->pi_StatefulAddrConf) { pi->pi_ra_flags |= ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER; start_dhcp(pi); } pi->pi_sol_state = DONE_SOLICIT; check_daemonize(); return (TIMER_INFINITY); } pi->pi_sol_time_left = ND_RTR_SOLICITATION_INTERVAL; return (pi->pi_sol_time_left); case NO_SOLICIT: case DONE_SOLICIT: return (TIMER_INFINITY); default: return (pi->pi_sol_time_left); } } /* * Timer mechanism using relative time (in milliseconds) from the * previous timer event. Timers exceeding TIMER_INFINITY milliseconds * will fire after TIMER_INFINITY milliseconds. */ static uint_t timer_previous; /* When last SIGALRM occurred */ static uint_t timer_next; /* Currently scheduled timeout */ static void timer_init(void) { timer_previous = getcurrenttime(); timer_next = TIMER_INFINITY; run_timeouts(); } /* * Make sure the next SIGALRM occurs delay milliseconds from the current * time if not earlier. * Handles getcurrenttime (32 bit integer holding milliseconds) wraparound * by treating differences greater than 0x80000000 as negative. */ void timer_schedule(uint_t delay) { uint_t now; struct itimerval itimerval; now = getcurrenttime(); if (debug & D_TIMER) { logmsg(LOG_DEBUG, "timer_schedule(%u): now %u next %u\n", delay, now, timer_next); } /* Will this timer occur before the currently scheduled SIGALRM? */ if (delay >= timer_next - now) { if (debug & D_TIMER) { logmsg(LOG_DEBUG, "timer_schedule(%u): no action - " "next in %u ms\n", delay, timer_next - now); } return; } if (delay == 0) { /* Minimum allowed delay */ delay = 1; } timer_next = now + delay; itimerval.it_value.tv_sec = delay / 1000; itimerval.it_value.tv_usec = (delay % 1000) * 1000; itimerval.it_interval.tv_sec = 0; itimerval.it_interval.tv_usec = 0; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "timer_schedule(%u): sec %lu usec %lu\n", delay, itimerval.it_value.tv_sec, itimerval.it_value.tv_usec); } if (setitimer(ITIMER_REAL, &itimerval, NULL) < 0) { logperror("timer_schedule: setitimer"); exit(2); } } /* * Conditional running of timer. If more than 'minimal_time' millseconds * since the timer routines were last run we run them. * Used when packets arrive. */ static void conditional_run_timeouts(uint_t minimal_time) { uint_t now; uint_t elapsed; now = getcurrenttime(); elapsed = now - timer_previous; if (elapsed > minimal_time) { if (debug & D_TIMER) { logmsg(LOG_DEBUG, "conditional_run_timeouts: " "elapsed %d\n", elapsed); } run_timeouts(); } } /* * Timer has fired. * Determine when the next timer event will occur by asking all * the timer routines. * Should not be called from a timer routine but in some cases this is * done because the code doesn't know that e.g. it was called from * ifconfig_timer(). In this case the nested run_timeouts will just return but * the running run_timeouts will ensure to call all the timer functions by * looping once more. */ static void run_timeouts(void) { uint_t now; uint_t elapsed; uint_t next; uint_t nexti; struct phyint *pi; struct phyint *next_pi; struct prefix *pr; struct prefix *next_pr; struct adv_prefix *adv_pr; struct adv_prefix *next_adv_pr; struct router *dr; struct router *next_dr; static boolean_t timeout_running; static boolean_t do_retry; if (timeout_running) { if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: nested call\n"); do_retry = _B_TRUE; return; } timeout_running = _B_TRUE; retry: /* How much time since the last time we were called? */ now = getcurrenttime(); elapsed = now - timer_previous; timer_previous = now; if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: elapsed %d\n", elapsed); next = TIMER_INFINITY; for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; nexti = phyint_timer(pi, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (pi %s): %d -> %u ms\n", pi->pi_name, nexti, next); } for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; nexti = prefix_timer(pr, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (pr %s): " "%d -> %u ms\n", pr->pr_name, nexti, next); } } for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = next_adv_pr) { next_adv_pr = adv_pr->adv_pr_next; nexti = adv_prefix_timer(adv_pr, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts " "(adv pr on %s): %d -> %u ms\n", adv_pr->adv_pr_physical->pi_name, nexti, next); } } for (dr = pi->pi_router_list; dr != NULL; dr = next_dr) { next_dr = dr->dr_next; nexti = router_timer(dr, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (dr): " "%d -> %u ms\n", nexti, next); } } if (pi->pi_TmpAddrsEnabled) { nexti = tmptoken_timer(pi, elapsed); if (nexti != TIMER_INFINITY && nexti < next) next = nexti; if (debug & D_TIMER) { logmsg(LOG_DEBUG, "run_timeouts (tmp on %s): " "%d -> %u ms\n", pi->pi_name, nexti, next); } } } /* * Make sure the timer functions are run at least once * an hour. */ if (next == TIMER_INFINITY) next = 3600 * 1000; /* 1 hour */ if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: %u ms\n", next); timer_schedule(next); if (do_retry) { if (debug & D_TIMER) logmsg(LOG_DEBUG, "run_timeouts: retry\n"); do_retry = _B_FALSE; goto retry; } timeout_running = _B_FALSE; } static int eventpipe_read = -1; /* Used for synchronous signal delivery */ static int eventpipe_write = -1; /* * Ensure that signals are processed synchronously with the rest of * the code by just writing a one character signal number on the pipe. * The poll loop will pick this up and process the signal event. */ static void sig_handler(int signo) { uchar_t buf = (uchar_t)signo; if (eventpipe_write == -1) { logmsg(LOG_ERR, "sig_handler: no pipe\n"); return; } if (write(eventpipe_write, &buf, sizeof (buf)) < 0) logperror("sig_handler: write"); } /* * Pick up a signal "byte" from the pipe and process it. */ static void in_signal(int fd) { uchar_t buf; struct phyint *pi; struct phyint *next_pi; switch (read(fd, &buf, sizeof (buf))) { case -1: logperror("in_signal: read"); exit(1); /* NOTREACHED */ case 1: break; case 0: logmsg(LOG_ERR, "in_signal: read eof\n"); exit(1); /* NOTREACHED */ default: logmsg(LOG_ERR, "in_signal: read > 1\n"); exit(1); } if (debug & D_TIMER) logmsg(LOG_DEBUG, "in_signal() got %d\n", buf); switch (buf) { case SIGALRM: if (debug & D_TIMER) { uint_t now = getcurrenttime(); logmsg(LOG_DEBUG, "in_signal(SIGALRM) delta %u\n", now - timer_next); } timer_next = TIMER_INFINITY; run_timeouts(); break; case SIGHUP: /* Re-read config file by exec'ing ourselves */ for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; if (pi->pi_AdvSendAdvertisements) check_to_advertise(pi, START_FINAL_ADV); /* * Remove all the configured addresses. * Remove the addrobj names created with ipmgmtd. * Release the dhcpv6 addresses if any. * Cleanup the phyints. */ phyint_delete(pi); } /* * Prevent fd leaks. Everything gets re-opened at start-up * time. 0, 1, and 2 are closed and re-opened as * /dev/null, so we'll leave those open. */ closefrom(3); logmsg(LOG_ERR, "SIGHUP: restart and reread config file\n"); (void) execv(argv0[0], argv0); _exit(0177); /* NOTREACHED */ case SIGUSR1: logmsg(LOG_DEBUG, "Printing configuration:\n"); phyint_print_all(); break; case SIGINT: case SIGTERM: case SIGQUIT: for (pi = phyints; pi != NULL; pi = next_pi) { next_pi = pi->pi_next; if (pi->pi_AdvSendAdvertisements) check_to_advertise(pi, START_FINAL_ADV); phyint_delete(pi); } (void) unlink(NDPD_SNMP_SOCKET); exit(0); /* NOTREACHED */ case 255: /* * Special "signal" from loopback_ra_enqueue. * Handle any queued loopback router advertisements. */ loopback_ra_dequeue(); break; default: logmsg(LOG_ERR, "in_signal: unknown signal: %d\n", buf); } } /* * Create pipe for signal delivery and set up signal handlers. */ static void setup_eventpipe(void) { int fds[2]; struct sigaction act; if ((pipe(fds)) < 0) { logperror("setup_eventpipe: pipe"); exit(1); } eventpipe_read = fds[0]; eventpipe_write = fds[1]; if (poll_add(eventpipe_read) == -1) { exit(1); } act.sa_handler = sig_handler; act.sa_flags = SA_RESTART; (void) sigaction(SIGALRM, &act, NULL); (void) sigset(SIGHUP, sig_handler); (void) sigset(SIGUSR1, sig_handler); (void) sigset(SIGTERM, sig_handler); (void) sigset(SIGINT, sig_handler); (void) sigset(SIGQUIT, sig_handler); } /* * Create a routing socket for receiving RTM_IFINFO messages and initialize * the routing socket message header and as much of the sockaddrs as possible. */ static int setup_rtsock(void) { int s; int ret; char *cp; struct sockaddr_in6 *sin6; s = socket(PF_ROUTE, SOCK_RAW, AF_INET6); if (s == -1) { logperror("socket(PF_ROUTE)"); exit(1); } ret = fcntl(s, F_SETFL, O_NDELAY|O_NONBLOCK); if (ret < 0) { logperror("fcntl(O_NDELAY)"); exit(1); } if (poll_add(s) == -1) { exit(1); } /* * Allocate storage for the routing socket message. */ rt_msg = (struct rt_msghdr *)malloc(NDP_RTM_MSGLEN); if (rt_msg == NULL) { logperror("malloc"); exit(1); } /* * Initialize the routing socket message by zero-filling it and then * setting the fields where are constant through the lifetime of the * process. */ bzero(rt_msg, NDP_RTM_MSGLEN); rt_msg->rtm_msglen = NDP_RTM_MSGLEN; rt_msg->rtm_version = RTM_VERSION; rt_msg->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFP; rt_msg->rtm_pid = getpid(); if (rt_msg->rtm_pid < 0) { logperror("getpid"); exit(1); } /* * The RTA_DST sockaddr does not change during the lifetime of the * process so it can be completely initialized at this time. */ cp = (char *)rt_msg + sizeof (struct rt_msghdr); sin6 = (struct sockaddr_in6 *)cp; sin6->sin6_family = AF_INET6; sin6->sin6_addr = in6addr_any; /* * Initialize the constant portion of the RTA_GATEWAY sockaddr. */ cp += sizeof (struct sockaddr_in6); rta_gateway = (struct sockaddr_in6 *)cp; rta_gateway->sin6_family = AF_INET6; /* * The RTA_NETMASK sockaddr does not change during the lifetime of the * process so it can be completely initialized at this time. */ cp += sizeof (struct sockaddr_in6); sin6 = (struct sockaddr_in6 *)cp; sin6->sin6_family = AF_INET6; sin6->sin6_addr = in6addr_any; /* * Initialize the constant portion of the RTA_IFP sockaddr. */ cp += sizeof (struct sockaddr_in6); rta_ifp = (struct sockaddr_dl *)cp; rta_ifp->sdl_family = AF_LINK; return (s); } static int setup_mibsock(void) { int sock; int ret; int len; struct sockaddr_un laddr; sock = socket(AF_UNIX, SOCK_DGRAM, 0); if (sock == -1) { logperror("setup_mibsock: socket(AF_UNIX)"); exit(1); } bzero(&laddr, sizeof (laddr)); laddr.sun_family = AF_UNIX; (void) strncpy(laddr.sun_path, NDPD_SNMP_SOCKET, sizeof (laddr.sun_path)); len = sizeof (struct sockaddr_un); (void) unlink(NDPD_SNMP_SOCKET); ret = bind(sock, (struct sockaddr *)&laddr, len); if (ret < 0) { logperror("setup_mibsock: bind\n"); exit(1); } ret = fcntl(sock, F_SETFL, O_NONBLOCK); if (ret < 0) { logperror("fcntl(O_NONBLOCK)"); exit(1); } if (poll_add(sock) == -1) { exit(1); } return (sock); } /* * Retrieve one routing socket message. If RTM_IFINFO indicates * new phyint do a full scan of the interfaces. If RTM_IFINFO * indicates an existing phyint, only scan that phyint and associated * prefixes. */ static void process_rtsock(int rtsock) { int n; #define MSG_SIZE 2048/8 int64_t msg[MSG_SIZE]; struct rt_msghdr *rtm; struct if_msghdr *ifm; struct phyint *pi; struct prefix *pr; boolean_t need_initifs = _B_FALSE; boolean_t need_ifscan = _B_FALSE; int64_t ifscan_msg[10][MSG_SIZE]; int ifscan_index = 0; int i; /* Empty the rtsock and coealesce all the work that we have */ while (ifscan_index < 10) { n = read(rtsock, msg, sizeof (msg)); if (n <= 0) { /* No more messages */ break; } rtm = (struct rt_msghdr *)msg; if (rtm->rtm_version != RTM_VERSION) { logmsg(LOG_ERR, "process_rtsock: version %d not understood\n", rtm->rtm_version); return; } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: /* * Some logical interface has changed - have to scan * everything to determine what actually changed. */ if (debug & D_IFSCAN) { logmsg(LOG_DEBUG, "process_rtsock: " "message %d\n", rtm->rtm_type); } need_initifs = _B_TRUE; break; case RTM_IFINFO: need_ifscan = _B_TRUE; (void) memcpy(ifscan_msg[ifscan_index], rtm, sizeof (msg)); ifscan_index++; /* Handled below */ break; default: /* Not interesting */ break; } } /* * If we do full scan i.e initifs, we don't need to * scan a particular interface as we should have * done that as part of initifs. */ if (need_initifs) { initifs(_B_FALSE); return; } if (!need_ifscan) return; for (i = 0; i < ifscan_index; i++) { ifm = (struct if_msghdr *)ifscan_msg[i]; if (debug & D_IFSCAN) logmsg(LOG_DEBUG, "process_rtsock: index %d\n", ifm->ifm_index); pi = phyint_lookup_on_index(ifm->ifm_index); if (pi == NULL) { /* * A new physical interface. Do a full scan of the * to catch any new logical interfaces. */ initifs(_B_FALSE); return; } if (ifm->ifm_flags != (uint_t)pi->pi_flags) { if (debug & D_IFSCAN) { logmsg(LOG_DEBUG, "process_rtsock: clr for " "%s old flags 0x%llx new flags 0x%x\n", pi->pi_name, pi->pi_flags, ifm->ifm_flags); } } /* * Mark the interfaces so that we can find phyints and prefixes * which have disappeared from the kernel. * if_process will set pr_in_use when it finds the * interface in the kernel. * Before re-examining the state of the interfaces, * PI_PRESENT should be cleared from pi_kernel_state. */ pi->pi_kernel_state &= ~PI_PRESENT; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { pr->pr_in_use = _B_FALSE; } if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { logperror("process_rtsock: socket"); return; } } if_process(ifsock, pi->pi_name, _B_FALSE); for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if_process(ifsock, pr->pr_name, _B_FALSE); } /* * If interface (still) exists in kernel, set * pi_state to indicate that. */ if (pi->pi_kernel_state & PI_PRESENT) { pi->pi_state |= PI_PRESENT; } check_if_removed(pi); if (show_ifs) phyint_print_all(); } } static void process_mibsock(int mibsock) { struct phyint *pi; socklen_t fromlen; struct sockaddr_un from; ndpd_info_t ndpd_info; ssize_t len; int command; fromlen = (socklen_t)sizeof (from); len = recvfrom(mibsock, &command, sizeof (int), 0, (struct sockaddr *)&from, &fromlen); if (len < sizeof (int) || command != NDPD_SNMP_INFO_REQ) { logperror("process_mibsock: bad command \n"); return; } ndpd_info.info_type = NDPD_SNMP_INFO_RESPONSE; ndpd_info.info_version = NDPD_SNMP_INFO_VER; ndpd_info.info_num_of_phyints = num_of_phyints; (void) sendto(mibsock, &ndpd_info, sizeof (ndpd_info_t), 0, (struct sockaddr *)&from, fromlen); for (pi = phyints; pi != NULL; pi = pi->pi_next) { int prefixes; int routers; struct prefix *prefix_list; struct router *router_list; ndpd_phyint_info_t phyint; ndpd_prefix_info_t prefix; ndpd_router_info_t router; /* * get number of prefixes */ routers = 0; prefixes = 0; prefix_list = pi->pi_prefix_list; while (prefix_list != NULL) { prefixes++; prefix_list = prefix_list->pr_next; } /* * get number of routers */ router_list = pi->pi_router_list; while (router_list != NULL) { routers++; router_list = router_list->dr_next; } phyint.phyint_info_type = NDPD_PHYINT_INFO; phyint.phyint_info_version = NDPD_PHYINT_INFO_VER; phyint.phyint_index = pi->pi_index; bcopy(pi->pi_config, phyint.phyint_config, I_IFSIZE); phyint.phyint_num_of_prefixes = prefixes; phyint.phyint_num_of_routers = routers; (void) sendto(mibsock, &phyint, sizeof (phyint), 0, (struct sockaddr *)&from, fromlen); /* * Copy prefix information */ prefix_list = pi->pi_prefix_list; while (prefix_list != NULL) { prefix.prefix_info_type = NDPD_PREFIX_INFO; prefix.prefix_info_version = NDPD_PREFIX_INFO_VER; prefix.prefix_prefix = prefix_list->pr_prefix; prefix.prefix_len = prefix_list->pr_prefix_len; prefix.prefix_flags = prefix_list->pr_flags; prefix.prefix_phyint_index = pi->pi_index; prefix.prefix_ValidLifetime = prefix_list->pr_ValidLifetime; prefix.prefix_PreferredLifetime = prefix_list->pr_PreferredLifetime; prefix.prefix_OnLinkLifetime = prefix_list->pr_OnLinkLifetime; prefix.prefix_OnLinkFlag = prefix_list->pr_OnLinkFlag; prefix.prefix_AutonomousFlag = prefix_list->pr_AutonomousFlag; (void) sendto(mibsock, &prefix, sizeof (prefix), 0, (struct sockaddr *)&from, fromlen); prefix_list = prefix_list->pr_next; } /* * Copy router information */ router_list = pi->pi_router_list; while (router_list != NULL) { router.router_info_type = NDPD_ROUTER_INFO; router.router_info_version = NDPD_ROUTER_INFO_VER; router.router_address = router_list->dr_address; router.router_lifetime = router_list->dr_lifetime; router.router_phyint_index = pi->pi_index; (void) sendto(mibsock, &router, sizeof (router), 0, (struct sockaddr *)&from, fromlen); router_list = router_list->dr_next; } } } /* * Look if the phyint or one of its prefixes have been removed from * the kernel and take appropriate action. * Uses pr_in_use and pi{,_kernel}_state. */ static void check_if_removed(struct phyint *pi) { struct prefix *pr, *next_pr; /* * Detect prefixes which are removed. * Static prefixes are just removed from our tables. * Non-static prefixes are recreated i.e. in.ndpd takes precedence * over manually removing prefixes via ifconfig. */ for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; if (!pr->pr_in_use) { /* Clear everything except PR_STATIC */ pr->pr_kernel_state &= PR_STATIC; if (pr->pr_state & PR_STATIC) prefix_update_ipadm_addrobj(pr, _B_FALSE); pr->pr_name[0] = '\0'; if (pr->pr_state & PR_STATIC) { prefix_delete(pr); } else if (!(pi->pi_kernel_state & PI_PRESENT)) { /* * Ensure that there are no future attempts to * run prefix_update_k since the phyint is gone. */ pr->pr_state = pr->pr_kernel_state; } else if (pr->pr_state != pr->pr_kernel_state) { logmsg(LOG_INFO, "Prefix manually removed " "on %s; recreating\n", pi->pi_name); prefix_update_k(pr); } } } /* * Detect phyints that have been removed from the kernel, and tear * down any prefixes we created that are associated with that phyint. * (NOTE: IPMP depends on in.ndpd tearing down these prefixes so an * administrator can easily place an IP interface with ADDRCONF'd * addresses into an IPMP group.) */ if (!(pi->pi_kernel_state & PI_PRESENT) && (pi->pi_state & PI_PRESENT)) { logmsg(LOG_ERR, "Interface %s has been removed from kernel. " "in.ndpd will no longer use it\n", pi->pi_name); for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; if (pr->pr_state & PR_AUTO) prefix_update_ipadm_addrobj(pr, _B_FALSE); prefix_delete(pr); } /* * Clear state so that should the phyint reappear we will * start with initial advertisements or solicitations. */ phyint_cleanup(pi); } } /* * Queuing mechanism for router advertisements that are sent by in.ndpd * and that also need to be processed by in.ndpd. * Uses "signal number" 255 to indicate to the main poll loop * that there is something to dequeue and send to incomining_ra(). */ struct raq { struct raq *raq_next; struct phyint *raq_pi; int raq_packetlen; uchar_t *raq_packet; }; static struct raq *raq_head = NULL; /* * Allocate a struct raq and memory for the packet. * Send signal 255 to have poll dequeue. */ static void loopback_ra_enqueue(struct phyint *pi, struct nd_router_advert *ra, int len) { struct raq *raq; struct raq **raqp; if (no_loopback) return; if (debug & D_PKTOUT) logmsg(LOG_DEBUG, "loopback_ra_enqueue for %s\n", pi->pi_name); raq = calloc(sizeof (struct raq), 1); if (raq == NULL) { logmsg(LOG_ERR, "loopback_ra_enqueue: out of memory\n"); return; } raq->raq_packet = malloc(len); if (raq->raq_packet == NULL) { free(raq); logmsg(LOG_ERR, "loopback_ra_enqueue: out of memory\n"); return; } bcopy(ra, raq->raq_packet, len); raq->raq_packetlen = len; raq->raq_pi = pi; /* Tail insert */ raqp = &raq_head; while (*raqp != NULL) raqp = &((*raqp)->raq_next); *raqp = raq; /* Signal for poll loop */ sig_handler(255); } /* * Dequeue and process all queued advertisements. */ static void loopback_ra_dequeue(void) { struct sockaddr_in6 from = IN6ADDR_LOOPBACK_INIT; struct raq *raq; if (debug & D_PKTIN) logmsg(LOG_DEBUG, "loopback_ra_dequeue()\n"); while ((raq = raq_head) != NULL) { raq_head = raq->raq_next; raq->raq_next = NULL; if (debug & D_PKTIN) { logmsg(LOG_DEBUG, "loopback_ra_dequeue for %s\n", raq->raq_pi->pi_name); } incoming_ra(raq->raq_pi, (struct nd_router_advert *)raq->raq_packet, raq->raq_packetlen, &from, _B_TRUE); free(raq->raq_packet); free(raq); } } static void usage(char *cmd) { (void) fprintf(stderr, "usage: %s [ -adt ] [-f ]\n", cmd); } int main(int argc, char *argv[]) { int i; struct phyint *pi; int c; char *config_file = PATH_NDPD_CONF; boolean_t file_required = _B_FALSE; argv0 = argv; srandom(gethostid()); (void) umask(0022); while ((c = getopt(argc, argv, "adD:ntIf:")) != EOF) { switch (c) { case 'a': /* * The StatelessAddrConf variable in ndpd.conf, if * present, will override this setting. */ ifdefaults[I_StatelessAddrConf].cf_value = 0; break; case 'd': debug = D_ALL; break; case 'D': i = strtol((char *)optarg, NULL, 0); if (i == 0) { (void) fprintf(stderr, "Bad debug flags: %s\n", (char *)optarg); exit(1); } debug |= i; break; case 'n': no_loopback = 1; break; case 'I': show_ifs = 1; break; case 't': debug |= D_PKTIN | D_PKTOUT | D_PKTBAD; break; case 'f': config_file = (char *)optarg; file_required = _B_TRUE; break; case '?': usage(argv[0]); exit(1); } } if (parse_config(config_file, file_required) == -1) exit(2); if (show_ifs) phyint_print_all(); if (debug == 0) initlog(); cmdsock = ndpd_setup_cmd_listener(); setup_eventpipe(); rtsock = setup_rtsock(); mibsock = setup_mibsock(); timer_init(); initifs(_B_TRUE); check_daemonize(); for (;;) { if (poll(pollfds, pollfd_num, -1) < 0) { if (errno == EINTR) continue; logperror("main: poll"); exit(1); } for (i = 0; i < pollfd_num; i++) { if (!(pollfds[i].revents & POLLIN)) continue; if (pollfds[i].fd == eventpipe_read) { in_signal(eventpipe_read); break; } if (pollfds[i].fd == rtsock) { process_rtsock(rtsock); break; } if (pollfds[i].fd == mibsock) { process_mibsock(mibsock); break; } if (pollfds[i].fd == cmdsock) { ndpd_cmd_handler(cmdsock); break; } /* * Run timer routine to advance clock if more than * half a second since the clock was advanced. * This limits CPU usage under severe packet * arrival rates but it creates a slight inaccuracy * in the timer mechanism. */ conditional_run_timeouts(500U); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pollfds[i].fd == pi->pi_sock) { in_data(pi); break; } } } } /* NOTREACHED */ return (0); } /* * LOGGER */ static boolean_t logging = _B_FALSE; static void initlog(void) { logging = _B_TRUE; openlog("in.ndpd", LOG_PID | LOG_CONS, LOG_DAEMON); } /* Print the date/time without a trailing carridge return */ static void fprintdate(FILE *file) { char buf[BUFSIZ]; struct tm tms; time_t now; now = time(NULL); (void) localtime_r(&now, &tms); (void) strftime(buf, sizeof (buf), "%h %d %X", &tms); (void) fprintf(file, "%s ", buf); } /* PRINTFLIKE2 */ void logmsg(int level, const char *fmt, ...) { va_list ap; va_start(ap, fmt); if (logging) { vsyslog(level, fmt, ap); } else { fprintdate(stderr); (void) vfprintf(stderr, fmt, ap); } va_end(ap); } void logperror(const char *str) { if (logging) { syslog(LOG_ERR, "%s: %m\n", str); } else { fprintdate(stderr); (void) fprintf(stderr, "%s: %s\n", str, strerror(errno)); } } void logperror_pi(const struct phyint *pi, const char *str) { if (logging) { syslog(LOG_ERR, "%s (interface %s): %m\n", str, pi->pi_name); } else { fprintdate(stderr); (void) fprintf(stderr, "%s (interface %s): %s\n", str, pi->pi_name, strerror(errno)); } } void logperror_pr(const struct prefix *pr, const char *str) { if (logging) { syslog(LOG_ERR, "%s (prefix %s if %s): %m\n", str, pr->pr_name, pr->pr_physical->pi_name); } else { fprintdate(stderr); (void) fprintf(stderr, "%s (prefix %s if %s): %s\n", str, pr->pr_name, pr->pr_physical->pi_name, strerror(errno)); } } static int ndpd_setup_cmd_listener(void) { int sock; int ret; struct sockaddr_un servaddr; sock = socket(AF_UNIX, SOCK_STREAM, 0); if (sock < 0) { logperror("socket"); exit(1); } bzero(&servaddr, sizeof (servaddr)); servaddr.sun_family = AF_UNIX; (void) strlcpy(servaddr.sun_path, IPADM_UDS_PATH, sizeof (servaddr.sun_path)); (void) unlink(servaddr.sun_path); ret = bind(sock, (struct sockaddr *)&servaddr, sizeof (servaddr)); if (ret < 0) { logperror("bind"); exit(1); } if (listen(sock, 30) < 0) { logperror("listen"); exit(1); } if (poll_add(sock) == -1) { logmsg(LOG_ERR, "command socket could not be added to the " "polling set\n"); exit(1); } return (sock); } /* * Commands received over the command socket come here */ static void ndpd_cmd_handler(int sock) { int newfd; struct sockaddr_storage peer; socklen_t peerlen; ipadm_ndpd_msg_t ndpd_msg; int retval; peerlen = sizeof (peer); newfd = accept(sock, (struct sockaddr *)&peer, &peerlen); if (newfd < 0) { logperror("accept"); return; } retval = ipadm_ndpd_read(newfd, &ndpd_msg, sizeof (ndpd_msg)); if (retval != 0) logperror("Could not read ndpd command"); retval = ndpd_process_cmd(newfd, &ndpd_msg); if (retval != 0) { logmsg(LOG_ERR, "ndpd command on interface %s failed with " "error %s\n", ndpd_msg.inm_ifname, strerror(retval)); } (void) close(newfd); } /* * Process the commands received from the cmd listener socket. */ static int ndpd_process_cmd(int newfd, ipadm_ndpd_msg_t *msg) { int err; if (!ipadm_check_auth()) { logmsg(LOG_ERR, "User not authorized to send the command\n"); (void) ndpd_send_error(newfd, EPERM); return (EPERM); } switch (msg->inm_cmd) { case IPADM_DISABLE_AUTOCONF: err = ndpd_set_autoconf(msg->inm_ifname, _B_FALSE); break; case IPADM_ENABLE_AUTOCONF: err = ndpd_set_autoconf(msg->inm_ifname, _B_TRUE); break; case IPADM_CREATE_ADDRS: err = ndpd_create_addrs(msg->inm_ifname, msg->inm_intfid, msg->inm_intfidlen, msg->inm_stateless, msg->inm_stateful, msg->inm_aobjname); break; case IPADM_DELETE_ADDRS: err = ndpd_delete_addrs(msg->inm_ifname); break; default: err = EINVAL; break; } (void) ndpd_send_error(newfd, err); return (err); } static int ndpd_send_error(int fd, int error) { return (ipadm_ndpd_write(fd, &error, sizeof (error))); } /* * Disables/Enables autoconfiguration of addresses on the * given physical interface. * This is provided to support the legacy method of configuring IPv6 * addresses. i.e. `ifconfig bge0 inet6 plumb` will plumb the interface * and start stateless and stateful autoconfiguration. If this function is * not called with enable=_B_FALSE, no autoconfiguration will be done until * ndpd_create_addrs() is called with an Interface ID. */ static int ndpd_set_autoconf(const char *ifname, boolean_t enable) { struct phyint *pi; pi = phyint_lookup((char *)ifname); if (pi == NULL) { /* * If the physical interface was plumbed but no * addresses were configured yet, phyint will not exist. */ pi = phyint_create((char *)ifname); if (pi == NULL) { logmsg(LOG_ERR, "could not create phyint for " "interface %s", ifname); return (ENOMEM); } } pi->pi_autoconf = enable; if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "ndpd_set_autoconf: %s autoconf for " "interface %s\n", (enable ? "enabled" : "disabled"), pi->pi_name); } return (0); } /* * Create auto-configured addresses on the given interface using * the given token as the interface id during the next Router Advertisement. * Currently, only one token per interface is supported. */ static int ndpd_create_addrs(const char *ifname, struct sockaddr_in6 intfid, int intfidlen, boolean_t stateless, boolean_t stateful, char *addrobj) { struct phyint *pi; struct lifreq lifr; struct sockaddr_in6 *sin6; int err; pi = phyint_lookup((char *)ifname); if (pi == NULL) { /* * If the physical interface was plumbed but no * addresses were configured yet, phyint will not exist. */ pi = phyint_create((char *)ifname); if (pi == NULL) { if (debug & D_PHYINT) logmsg(LOG_ERR, "could not create phyint " "for interface %s", ifname); return (ENOMEM); } } else if (pi->pi_autoconf) { logmsg(LOG_ERR, "autoconfiguration already in progress\n"); return (EEXIST); } check_autoconf_var_consistency(pi, stateless, stateful); if (intfidlen == 0) { pi->pi_default_token = _B_TRUE; if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { err = errno; logperror("ndpd_create_addrs: socket"); return (err); } } (void) strncpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; if (ioctl(ifsock, SIOCGLIFTOKEN, (char *)&lifr) < 0) { err = errno; logperror("SIOCGLIFTOKEN"); return (err); } pi->pi_token = sin6->sin6_addr; pi->pi_token_length = lifr.lifr_addrlen; } else { pi->pi_default_token = _B_FALSE; pi->pi_token = intfid.sin6_addr; pi->pi_token_length = intfidlen; } pi->pi_stateless = stateless; pi->pi_stateful = stateful; (void) strlcpy(pi->pi_ipadm_aobjname, addrobj, sizeof (pi->pi_ipadm_aobjname)); /* We can allow autoconfiguration now. */ pi->pi_autoconf = _B_TRUE; /* Restart the solicitations. */ if (pi->pi_sol_state == DONE_SOLICIT) pi->pi_sol_state = NO_SOLICIT; if (pi->pi_sol_state == NO_SOLICIT) check_to_solicit(pi, START_INIT_SOLICIT); if (debug & D_PHYINT) logmsg(LOG_DEBUG, "ndpd_create_addrs: " "added token to interface %s\n", pi->pi_name); return (0); } /* * This function deletes all addresses on the given interface * with the given Interface ID. */ static int ndpd_delete_addrs(const char *ifname) { struct phyint *pi; struct prefix *pr, *next_pr; struct lifreq lifr; int err; pi = phyint_lookup((char *)ifname); if (pi == NULL) { logmsg(LOG_ERR, "no phyint found for %s", ifname); return (ENXIO); } if (IN6_IS_ADDR_UNSPECIFIED(&pi->pi_token)) { logmsg(LOG_ERR, "token does not exist for %s", ifname); return (ENOENT); } if (ifsock < 0) { ifsock = socket(AF_INET6, SOCK_DGRAM, 0); if (ifsock < 0) { err = errno; logperror("ndpd_delete_addrs: socket"); return (err); } } /* Remove the prefixes for this phyint if they exist */ for (pr = pi->pi_prefix_list; pr != NULL; pr = next_pr) { next_pr = pr->pr_next; if (pr->pr_name[0] == '\0') { prefix_delete(pr); continue; } /* * Delete all the prefixes for the auto-configured * addresses as well as the DHCPv6 addresses. */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); if (ioctl(ifsock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { err = errno; logperror("SIOCGLIFFLAGS"); return (err); } if ((lifr.lifr_flags & IFF_ADDRCONF) || (lifr.lifr_flags & IFF_DHCPRUNNING)) { prefix_update_ipadm_addrobj(pr, _B_FALSE); } prefix_delete(pr); } /* * If we had started dhcpagent, we need to release the leases * if any are required. */ if (pi->pi_stateful) { (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); if (ioctl(ifsock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { err = errno; logperror("SIOCGLIFFLAGS"); return (err); } if (lifr.lifr_flags & IFF_DHCPRUNNING) release_dhcp(pi); } /* * Reset the Interface ID on this phyint and stop autoconfigurations * until a new interface ID is provided. */ pi->pi_token = in6addr_any; pi->pi_ifaddr = in6addr_any; pi->pi_token_length = 0; pi->pi_autoconf = _B_FALSE; pi->pi_ipadm_aobjname[0] = '\0'; /* Reset the stateless and stateful settings to default. */ pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "ndpd_delete_addrs: " "removed token from interface %s\n", pi->pi_name); } return (0); } void check_autoconf_var_consistency(struct phyint *pi, boolean_t stateless, boolean_t stateful) { /* * If StatelessAddrConf and StatelessAddrConf are set in * /etc/inet/ndpd.conf, check if the new values override those * settings. If so, log a warning. */ if ((pi->pi_StatelessAddrConf != ifdefaults[I_StatelessAddrConf].cf_value && stateless != pi->pi_StatelessAddrConf) || (pi->pi_StatefulAddrConf != ifdefaults[I_StatefulAddrConf].cf_value && stateful != pi->pi_StatefulAddrConf)) { logmsg(LOG_ERR, "check_autoconf_var_consistency: " "Overriding the StatelessAddrConf or StatefulAddrConf " "settings in ndpd.conf with the new values for " "interface %s\n", pi->pi_name); } } /* * If ipadm was used to start autoconfiguration and in.ndpd was restarted * for some reason, in.ndpd has to resume autoconfiguration when it comes up. * In this function, it scans the ipadm_addr_info() output to find a link-local * on this interface with address type "addrconf" and extracts the interface id. * It also stores the addrobj name to be used later when new addresses are * created for the prefixes advertised by the router. * If autoconfiguration was never started on this interface before in.ndpd * was killed, then in.ndpd should refrain from configuring prefixes, even if * there is a valid link-local on this interface, created by ipadm (identified * if there is a valid addrobj name). */ static int phyint_check_ipadm_intfid(struct phyint *pi) { ipadm_status_t status; ipadm_addr_info_t *addrinfo; struct ifaddrs *ifap; ipadm_addr_info_t *ainfop; struct sockaddr_in6 *sin6; ipadm_handle_t iph; if (ipadm_open(&iph, 0) != IPADM_SUCCESS) { logmsg(LOG_ERR, "could not open handle to libipadm\n"); return (-1); } status = ipadm_addr_info(iph, pi->pi_name, &addrinfo, IPADM_OPT_ZEROADDR, LIFC_NOXMIT|LIFC_TEMPORARY); if (status != IPADM_SUCCESS) { ipadm_close(iph); return (-1); } pi->pi_autoconf = _B_TRUE; for (ainfop = addrinfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) { ifap = &ainfop->ia_ifa; if (ifap->ifa_addr->sa_family != AF_INET6 || ainfop->ia_state == IFA_DISABLED) continue; sin6 = (struct sockaddr_in6 *)ifap->ifa_addr; if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { if (ainfop->ia_atype == IPADM_ADDR_IPV6_ADDRCONF) { /* * Clearing pi_default_token here * prevents the configured interface * token from being overwritten later. */ pi->pi_default_token = _B_FALSE; pi->pi_token = sin6->sin6_addr; pi->pi_token._S6_un._S6_u32[0] = 0; pi->pi_token._S6_un._S6_u32[1] = 0; pi->pi_autoconf = _B_TRUE; (void) strlcpy(pi->pi_ipadm_aobjname, ainfop->ia_aobjname, sizeof (pi->pi_ipadm_aobjname)); break; } /* * If IFF_NOLINKLOCAL is set, then the link-local * was created using ipadm. Do not autoconfigure until * ipadm is explicitly used for autoconfiguration. */ if (ifap->ifa_flags & IFF_NOLINKLOCAL) pi->pi_autoconf = _B_FALSE; } else if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) && strrchr(ifap->ifa_name, ':') == NULL) { /* The interface was created using ipadm. */ pi->pi_autoconf = _B_FALSE; } } ipadm_free_addr_info(addrinfo); if (!pi->pi_autoconf) { pi->pi_token = in6addr_any; pi->pi_token_length = 0; } ipadm_close(iph); 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Copyright 2015, OmniTI Computer Consulting, Inc. All rights reserved. */ #include "defs.h" #include "tables.h" #include #include #include static boolean_t verify_opt_len(struct nd_opt_hdr *opt, int optlen, struct phyint *pi, struct sockaddr_in6 *from); static void incoming_rs(struct phyint *pi, struct nd_router_solicit *rs, int len, struct sockaddr_in6 *from); void incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from, boolean_t loopback); static void incoming_prefix_opt(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback); static void incoming_prefix_onlink(struct phyint *pi, uchar_t *opt); void incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt); static void incoming_prefix_stateful(struct phyint *, uchar_t *); static boolean_t incoming_prefix_addrconf(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback); boolean_t incoming_prefix_addrconf_process(struct phyint *pi, struct prefix *pr, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback, boolean_t new_prefix); static void incoming_mtu_opt(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from); static void incoming_lla_opt(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, int isrouter); static void verify_ra_consistency(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from); static void verify_prefix_opt(struct phyint *pi, uchar_t *opt, char *frombuf); static void verify_mtu_opt(struct phyint *pi, uchar_t *opt, char *frombuf); static void update_ra_flag(const struct phyint *pi, const struct sockaddr_in6 *from, int isrouter); /* * Return a pointer to the specified option buffer. * If not found return NULL. */ static void * find_ancillary(struct msghdr *msg, int cmsg_type) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(msg); cmsg != NULL; cmsg = CMSG_NXTHDR(msg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == cmsg_type) { return (CMSG_DATA(cmsg)); } } return (NULL); } void in_data(struct phyint *pi) { struct sockaddr_in6 from; struct icmp6_hdr *icmp; struct nd_router_solicit *rs; struct nd_router_advert *ra; static uint64_t in_packet[(IP_MAXPACKET + 1)/8]; static uint64_t ancillary_data[(IP_MAXPACKET + 1)/8]; int len; char abuf[INET6_ADDRSTRLEN]; const char *msgbuf; struct msghdr msg; struct iovec iov; uchar_t *opt; uint_t hoplimit; iov.iov_base = (char *)in_packet; iov.iov_len = sizeof (in_packet); msg.msg_iov = &iov; msg.msg_iovlen = 1; msg.msg_name = (struct sockaddr *)&from; msg.msg_namelen = sizeof (from); msg.msg_control = ancillary_data; msg.msg_controllen = sizeof (ancillary_data); if ((len = recvmsg(pi->pi_sock, &msg, 0)) < 0) { logperror_pi(pi, "in_data: recvfrom"); return; } if (len == 0) return; if (inet_ntop(AF_INET6, (void *)&from.sin6_addr, abuf, sizeof (abuf)) == NULL) msgbuf = "Unspecified Router"; else msgbuf = abuf; /* Ignore packets > 64k or control buffers that don't fit */ if (msg.msg_flags & (MSG_TRUNC|MSG_CTRUNC)) { if (debug & D_PKTBAD) { logmsg(LOG_DEBUG, "Truncated message: msg_flags 0x%x " "from %s\n", msg.msg_flags, msgbuf); } return; } icmp = (struct icmp6_hdr *)in_packet; if (len < ICMP6_MINLEN) { logmsg(LOG_INFO, "Too short ICMP packet: %d bytes " "from %s on %s\n", len, msgbuf, pi->pi_name); return; } opt = find_ancillary(&msg, IPV6_HOPLIMIT); if (opt == NULL) { /* Unknown hoplimit - must drop */ logmsg(LOG_INFO, "Unknown hop limit from %s on %s\n", msgbuf, pi->pi_name); return; } hoplimit = *(uint_t *)opt; opt = find_ancillary(&msg, IPV6_RTHDR); if (opt != NULL) { /* Can't allow routing headers in ND messages */ logmsg(LOG_INFO, "ND message with routing header " "from %s on %s\n", msgbuf, pi->pi_name); return; } switch (icmp->icmp6_type) { case ND_ROUTER_SOLICIT: if (!pi->pi_AdvSendAdvertisements) return; if (pi->pi_flags & IFF_NORTEXCH) { if (debug & D_PKTIN) { logmsg(LOG_DEBUG, "Ignore received RS packet " "on %s (no route exchange on interface)\n", pi->pi_name); } return; } /* * Assumes that the kernel has verified the AH (if present) * and the ICMP checksum. */ if (hoplimit != IPV6_MAX_HOPS) { logmsg(LOG_DEBUG, "RS hop limit: %d from %s on %s\n", hoplimit, msgbuf, pi->pi_name); return; } if (icmp->icmp6_code != 0) { logmsg(LOG_INFO, "RS code: %d from %s on %s\n", icmp->icmp6_code, msgbuf, pi->pi_name); return; } if (len < sizeof (struct nd_router_solicit)) { logmsg(LOG_INFO, "RS too short: %d bytes " "from %s on %s\n", len, msgbuf, pi->pi_name); return; } rs = (struct nd_router_solicit *)icmp; if (len > sizeof (struct nd_router_solicit)) { if (!verify_opt_len((struct nd_opt_hdr *)&rs[1], len - sizeof (struct nd_router_solicit), pi, &from)) return; } if (debug & D_PKTIN) { print_route_sol("Received valid solicit from ", pi, rs, len, &from); } incoming_rs(pi, rs, len, &from); break; case ND_ROUTER_ADVERT: if (IN6_IS_ADDR_UNSPECIFIED(&from.sin6_addr)) { /* * Router advt. must have address! * Logging the news and returning. */ logmsg(LOG_DEBUG, "Router's address unspecified in advertisement\n"); return; } if (pi->pi_flags & IFF_NORTEXCH) { if (debug & D_PKTIN) { logmsg(LOG_DEBUG, "Ignore received RA packet " "on %s (no route exchange on interface)\n", pi->pi_name); } return; } /* * Assumes that the kernel has verified the AH (if present) * and the ICMP checksum. */ if (!IN6_IS_ADDR_LINKLOCAL(&from.sin6_addr)) { logmsg(LOG_DEBUG, "RA from %s - not link local on %s\n", msgbuf, pi->pi_name); return; } if (hoplimit != IPV6_MAX_HOPS) { logmsg(LOG_INFO, "RA hop limit: %d from %s on %s\n", hoplimit, msgbuf, pi->pi_name); return; } if (icmp->icmp6_code != 0) { logmsg(LOG_INFO, "RA code: %d from %s on %s\n", icmp->icmp6_code, msgbuf, pi->pi_name); return; } if (len < sizeof (struct nd_router_advert)) { logmsg(LOG_INFO, "RA too short: %d bytes " "from %s on %s\n", len, msgbuf, pi->pi_name); return; } ra = (struct nd_router_advert *)icmp; if (len > sizeof (struct nd_router_advert)) { if (!verify_opt_len((struct nd_opt_hdr *)&ra[1], len - sizeof (struct nd_router_advert), pi, &from)) return; } if (debug & D_PKTIN) { print_route_adv("Received valid advert from ", pi, ra, len, &from); } if (pi->pi_AdvSendAdvertisements) verify_ra_consistency(pi, ra, len, &from); else incoming_ra(pi, ra, len, &from, _B_FALSE); break; } } /* * Process a received router solicitation. * Check for source link-layer address option and check if it * is time to advertise. */ static void incoming_rs(struct phyint *pi, struct nd_router_solicit *rs, int len, struct sockaddr_in6 *from) { struct nd_opt_hdr *opt; int optlen; /* Process any options */ len -= sizeof (struct nd_router_solicit); opt = (struct nd_opt_hdr *)&rs[1]; while (len >= sizeof (struct nd_opt_hdr)) { optlen = opt->nd_opt_len * 8; switch (opt->nd_opt_type) { case ND_OPT_SOURCE_LINKADDR: incoming_lla_opt(pi, (uchar_t *)opt, from, NDF_ISROUTER_OFF); break; default: break; } opt = (struct nd_opt_hdr *)((char *)opt + optlen); len -= optlen; } /* Simple algorithm: treat unicast and multicast RSs the same */ check_to_advertise(pi, RECEIVED_SOLICIT); } /* * Function that sends commands to dhcpagent daemon. */ int dhcp_op(struct phyint *pi, int type) { dhcp_ipc_request_t *request; dhcp_ipc_reply_t *reply = NULL; int error; request = dhcp_ipc_alloc_request(type | DHCP_V6, pi->pi_name, NULL, 0, DHCP_TYPE_NONE); if (request == NULL) { logmsg(LOG_ERR, "dhcp_op: out of memory\n"); /* make sure we try again next time there's a chance */ if (type != DHCP_RELEASE) { pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER; } return (DHCP_IPC_E_MEMORY); } error = dhcp_ipc_make_request(request, &reply, 0); free(request); if (error != 0) { logmsg(LOG_ERR, "could not send request to dhcpagent: " "%s: %s\n", pi->pi_name, dhcp_ipc_strerror(error)); return (error); } error = reply->return_code; free(reply); return (error); } /* * Start up DHCPv6 on a given physical interface. Does not wait for * a message to be returned from the daemon. */ void start_dhcp(struct phyint *pi) { int error; int type; if (dhcp_start_agent(DHCP_IPC_MAX_WAIT) == -1) { logmsg(LOG_ERR, "unable to start %s\n", DHCP_AGENT_PATH); /* make sure we try again next time there's a chance */ pi->pi_ra_flags &= ~ND_RA_FLAG_MANAGED & ~ND_RA_FLAG_OTHER; return; } else if (pi->pi_ra_flags & ND_RA_FLAG_MANAGED) type = DHCP_START; else type = DHCP_INFORM; error = dhcp_op(pi, type); /* * Timeout is considered to be "success" because we don't wait for DHCP * to do its exchange. */ if (error != DHCP_IPC_SUCCESS && error != DHCP_IPC_E_RUNNING && error != DHCP_IPC_E_TIMEOUT) { logmsg(LOG_ERR, "Error in dhcpagent: %s: %s\n", pi->pi_name, dhcp_ipc_strerror(error)); } } /* * Release the acquired DHCPv6 lease on a given physical interface. * Does not wait for a message to be returned from the daemon. */ void release_dhcp(struct phyint *pi) { int error; int type; type = DHCP_RELEASE; retry: error = dhcp_op(pi, type); if (error != DHCP_IPC_SUCCESS && error != DHCP_IPC_E_RUNNING && error != DHCP_IPC_E_TIMEOUT) { if (type == DHCP_RELEASE && error == DHCP_IPC_E_OUTSTATE) { /* * Drop the dhcp control if we cannot release it. */ type = DHCP_DROP; goto retry; } logmsg(LOG_ERR, "Error in dhcpagent: %s: %s\n", pi->pi_name, dhcp_ipc_strerror(error)); } } /* * Globals to check if we're seeing unusual hop counts in Router * Advertisements (RAs). We record the hopcounts in the kernel using * SIOCSLIFLNKINFO, but the kernel ignores these when actually setting IPv6 * hop counts for packets. * * RFC 3756 does mention the possibility of an adversary throttling down * hopcounts using unsolicited RAs. These variables can be tuned with 'mdb -p' * to reduce/increase our logging threshholds. */ /* Really a boolean... if set, also log the offending sending address. */ int bad_hopcount_record_addr = 0; /* Anything less triggers a warning. Set to 0 to disable. */ int bad_hopcount_threshhold = 16; /* Number of packets received below the threshhold. */ uint64_t bad_hopcount_packets; /* * Process a received router advertisement. * Called both when packets arrive as well as when we send RAs. * In the latter case 'loopback' is set. */ void incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from, boolean_t loopback) { struct nd_opt_hdr *opt; int optlen; struct lifreq lifr; boolean_t set_needed = _B_FALSE; struct router *dr; uint16_t router_lifetime; uint_t reachable, retrans; boolean_t reachable_time_changed = _B_FALSE; boolean_t slla_opt_present = _B_FALSE; if (no_loopback && loopback) return; bzero(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); if (ra->nd_ra_curhoplimit != CURHOP_UNSPECIFIED && ra->nd_ra_curhoplimit != pi->pi_CurHopLimit) { pi->pi_CurHopLimit = ra->nd_ra_curhoplimit; lifr.lifr_ifinfo.lir_maxhops = pi->pi_CurHopLimit; set_needed = _B_TRUE; if (pi->pi_CurHopLimit < bad_hopcount_threshhold) { char abuf[INET6_ADDRSTRLEN]; bad_hopcount_packets++; logmsg(LOG_ALERT, "Low hopcount %d received on %s%s%s\n", pi->pi_CurHopLimit, pi->pi_name, bad_hopcount_record_addr ? " from " : "", bad_hopcount_record_addr ? inet_ntop(AF_INET6, &from->sin6_addr, abuf, INET6_ADDRSTRLEN) : ""); } } reachable = ntohl(ra->nd_ra_reachable); if (reachable != 0 && reachable != pi->pi_BaseReachableTime) { pi->pi_BaseReachableTime = reachable; reachable_time_changed = _B_TRUE; } if (pi->pi_reach_time_since_random < MIN_REACH_RANDOM_INTERVAL || reachable_time_changed) { phyint_reach_random(pi, _B_FALSE); set_needed = _B_TRUE; } lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; retrans = ntohl(ra->nd_ra_retransmit); if (retrans != 0 && pi->pi_RetransTimer != retrans) { pi->pi_RetransTimer = retrans; lifr.lifr_ifinfo.lir_reachretrans = pi->pi_RetransTimer; set_needed = _B_TRUE; } if (set_needed) { if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "incoming_ra: SIOCSLIFLNKINFO"); return; } } /* * If the "managed" flag is set, then just assume that the "other" flag * is set as well. It's not legal to get addresses alone without * getting other data. */ if (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) ra->nd_ra_flags_reserved |= ND_RA_FLAG_OTHER; /* * If either the "managed" or "other" bits have turned on, then it's * now time to invoke DHCP. If only the "other" bit is set, then don't * get addresses via DHCP; only "other" data. If "managed" is set, * then we must always get both addresses and "other" data. */ if (pi->pi_autoconf && pi->pi_stateful && (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags & (ND_RA_FLAG_MANAGED | ND_RA_FLAG_OTHER))) { if (debug & D_DHCP) { logmsg(LOG_DEBUG, "incoming_ra: trigger dhcp %s on %s\n", (ra->nd_ra_flags_reserved & ~pi->pi_ra_flags & ND_RA_FLAG_MANAGED) ? "MANAGED" : "OTHER", pi->pi_name); } pi->pi_ra_flags |= ra->nd_ra_flags_reserved; start_dhcp(pi); } /* Skip default router code if sent from ourselves */ if (!loopback) { /* Find and update or add default router in list */ dr = router_lookup(pi, from->sin6_addr); router_lifetime = ntohs(ra->nd_ra_router_lifetime); if (dr == NULL) { if (router_lifetime != 0) { dr = router_create(pi, from->sin6_addr, MILLISEC * router_lifetime); timer_schedule(dr->dr_lifetime); } } else { dr->dr_lifetime = MILLISEC * router_lifetime; if (dr->dr_lifetime != 0) timer_schedule(dr->dr_lifetime); if ((dr->dr_lifetime != 0 && !dr->dr_inkernel) || (dr->dr_lifetime == 0 && dr->dr_inkernel)) router_update_k(dr); } } /* Process any options */ len -= sizeof (struct nd_router_advert); opt = (struct nd_opt_hdr *)&ra[1]; while (len >= sizeof (struct nd_opt_hdr)) { optlen = opt->nd_opt_len * 8; switch (opt->nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: incoming_prefix_opt(pi, (uchar_t *)opt, from, loopback); break; case ND_OPT_MTU: incoming_mtu_opt(pi, (uchar_t *)opt, from); break; case ND_OPT_SOURCE_LINKADDR: /* skip lla option if sent from ourselves! */ if (!loopback) { incoming_lla_opt(pi, (uchar_t *)opt, from, NDF_ISROUTER_ON); slla_opt_present = _B_TRUE; } break; default: break; } opt = (struct nd_opt_hdr *)((char *)opt + optlen); len -= optlen; } if (!loopback && !slla_opt_present) update_ra_flag(pi, from, NDF_ISROUTER_ON); /* Stop sending solicitations */ check_to_solicit(pi, SOLICIT_DONE); } /* * Process a received prefix option. * Unless addrconf is turned off we process both the addrconf and the * onlink aspects of the prefix option. * * Note that when a flag (onlink or auto) is turned off we do nothing - * the prefix will time out. */ static void incoming_prefix_opt(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; boolean_t good_prefix = _B_TRUE; if (8 * po->nd_opt_pi_len != sizeof (*po)) { char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "prefix option from %s on %s wrong size " "(%d bytes)\n", abuf, pi->pi_name, 8 * (int)po->nd_opt_pi_len); return; } if (IN6_IS_ADDR_LINKLOCAL(&po->nd_opt_pi_prefix)) { char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "RA from %s on %s contains link-local prefix " "- ignored\n", abuf, pi->pi_name); return; } if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) && pi->pi_stateless && pi->pi_autoconf) { good_prefix = incoming_prefix_addrconf(pi, opt, from, loopback); } if ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) && good_prefix) { incoming_prefix_onlink(pi, opt); } if (pi->pi_stateful && pi->pi_autoconf) incoming_prefix_stateful(pi, opt); } /* * Process prefix options with the onlink flag set. * * If there are no routers ndpd will add an onlink * default route which will allow communication * between neighbors. * * This function needs to loop to find the same prefix multiple times * as if a failover happened earlier, the addresses belonging to * a different interface may be found here on this interface. */ static void incoming_prefix_onlink(struct phyint *pi, uchar_t *opt) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; int plen; struct prefix *pr; uint32_t validtime; /* Without 2 hour rule */ boolean_t found_one = _B_FALSE; plen = po->nd_opt_pi_prefix_len; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (pr->pr_prefix_len == plen && prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, plen)) { /* Exclude static prefixes */ if (pr->pr_state & PR_STATIC) continue; found_one = _B_TRUE; incoming_prefix_onlink_process(pr, opt); } } validtime = ntohl(po->nd_opt_pi_valid_time); /* * If we have found a matching prefix already or validtime * is zero, we have nothing to do. */ if (validtime == 0 || found_one) return; pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, 0); if (pr == NULL) return; incoming_prefix_onlink_process(pr, opt); } void incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; uint32_t validtime; /* Without 2 hour rule */ char abuf[INET6_ADDRSTRLEN]; validtime = ntohl(po->nd_opt_pi_valid_time); if (validtime != 0) pr->pr_state |= PR_ONLINK; else pr->pr_state &= ~PR_ONLINK; /* * Convert from seconds to milliseconds avoiding overflow. * If the lifetime in the packet is e.g. PREFIX_INFINITY - 1 * (4 billion seconds - about 130 years) we will in fact time * out the prefix after 4 billion milliseconds - 46 days). * Thus the longest lifetime (apart from infinity) is 46 days. * Note that this ensures that PREFIX_INFINITY still means "forever". */ if (pr->pr_flags & IFF_TEMPORARY) { pr->pr_OnLinkLifetime = pr->pr_ValidLifetime; } else { if (validtime >= PREFIX_INFINITY / MILLISEC) pr->pr_OnLinkLifetime = PREFIX_INFINITY - 1; else pr->pr_OnLinkLifetime = validtime * MILLISEC; } pr->pr_OnLinkFlag = _B_TRUE; if (debug & (D_PREFIX|D_TMP)) { logmsg(LOG_DEBUG, "incoming_prefix_onlink_process(%s, %s/%u) " "onlink %u state 0x%x, kstate 0x%x\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, pr->pr_OnLinkLifetime, pr->pr_state, pr->pr_kernel_state); } if (pr->pr_kernel_state != pr->pr_state) { prefix_update_k(pr); } if (pr->pr_OnLinkLifetime != 0) timer_schedule(pr->pr_OnLinkLifetime); } /* * Process all prefix options by locating the DHCPv6-configured interfaces, and * applying the netmasks as needed. */ static void incoming_prefix_stateful(struct phyint *pi, uchar_t *opt) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; struct prefix *pr; boolean_t foundpref; char abuf[INET6_ADDRSTRLEN]; /* Make sure it's a valid prefix. */ if (ntohl(po->nd_opt_pi_valid_time) == 0) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "incoming_prefix_stateful: ignoring " "prefix with no valid time\n"); return; } if (debug & D_DHCP) logmsg(LOG_DEBUG, "incoming_prefix_stateful(%s, %s/%d)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, abuf, sizeof (abuf)), po->nd_opt_pi_prefix_len); foundpref = _B_FALSE; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, po->nd_opt_pi_prefix_len)) { if ((pr->pr_flags & IFF_DHCPRUNNING) && pr->pr_prefix_len != po->nd_opt_pi_prefix_len) { pr->pr_prefix_len = po->nd_opt_pi_prefix_len; if (pr->pr_flags & IFF_UP) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "incoming_prefix_stateful:" " set mask on DHCP %s\n", pr->pr_name); prefix_update_dhcp(pr); } } if (pr->pr_prefix_len == po->nd_opt_pi_prefix_len && (!(pr->pr_state & PR_STATIC) || (pr->pr_flags & IFF_DHCPRUNNING))) foundpref = _B_TRUE; } } /* * If there's no matching DHCPv6 prefix present, then create an empty * one so that we'll be able to configure it later. */ if (!foundpref) { pr = prefix_create(pi, po->nd_opt_pi_prefix, po->nd_opt_pi_prefix_len, IFF_DHCPRUNNING); if (pr != NULL) { pr->pr_state = PR_STATIC; if (debug & D_DHCP) logmsg(LOG_DEBUG, "incoming_prefix_stateful: created dummy " "prefix for later\n"); } } } /* * Process prefix options with the autonomous flag set. * Returns false if this prefix results in a bad address (duplicate) * This function needs to loop to find the same prefix multiple times * as if a failover happened earlier, the addresses belonging to * a different interface may be found here on this interface. */ static boolean_t incoming_prefix_addrconf(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; int plen; struct prefix *pr; uint32_t validtime, preftime; /* In seconds */ char abuf[INET6_ADDRSTRLEN]; char pbuf[INET6_ADDRSTRLEN]; boolean_t found_pub = _B_FALSE; boolean_t found_tmp = _B_FALSE; boolean_t ret; validtime = ntohl(po->nd_opt_pi_valid_time); preftime = ntohl(po->nd_opt_pi_preferred_time); plen = po->nd_opt_pi_prefix_len; /* Sanity checks */ if (validtime < preftime) { (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); (void) inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, pbuf, sizeof (pbuf)); logmsg(LOG_WARNING, "prefix option %s/%u from %s on %s: " "valid %u < pref %u ignored\n", pbuf, plen, abuf, pi->pi_name, validtime, preftime); return (_B_FALSE); } for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (pr->pr_prefix_len == plen && prefix_equal(po->nd_opt_pi_prefix, pr->pr_prefix, plen)) { /* Exclude static prefixes and DHCP */ if ((pr->pr_state & PR_STATIC) || (pr->pr_flags & IFF_DHCPRUNNING)) continue; if (pr->pr_flags & IFF_TEMPORARY) { /* * If this address is deprecated and its token * doesn't match the current tmp token, we want * to create a new address with the current * token. So don't count this addr as a match. */ if (!((pr->pr_flags & IFF_DEPRECATED) && !token_equal(pi->pi_tmp_token, pr->pr_address, TMP_TOKEN_BITS))) found_tmp = _B_TRUE; } else { found_pub = _B_TRUE; } (void) incoming_prefix_addrconf_process(pi, pr, opt, from, loopback, _B_FALSE); } } /* * If we have found a matching prefix (for public and, if temp addrs * are enabled, for temporary) already or validtime is zero, we have * nothing to do. */ if (validtime == 0 || (found_pub && (!pi->pi_TmpAddrsEnabled || found_tmp))) return (_B_TRUE); if (!found_pub) { pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, 0); if (pr == NULL) return (_B_TRUE); ret = incoming_prefix_addrconf_process(pi, pr, opt, from, loopback, _B_TRUE); } /* * if processing of the public address failed, * don't bother with the temporary address. */ if (ret == _B_FALSE) return (_B_FALSE); if (pi->pi_TmpAddrsEnabled && !found_tmp) { pr = prefix_create(pi, po->nd_opt_pi_prefix, plen, IFF_TEMPORARY); if (pr == NULL) return (_B_TRUE); ret = incoming_prefix_addrconf_process(pi, pr, opt, from, loopback, _B_TRUE); } return (ret); } boolean_t incoming_prefix_addrconf_process(struct phyint *pi, struct prefix *pr, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback, boolean_t new_prefix) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; char abuf[INET6_ADDRSTRLEN]; char pbuf[INET6_ADDRSTRLEN]; uint32_t validtime, preftime; /* In seconds */ uint32_t recorded_validtime; /* In seconds */ int plen; struct prefix *other_pr; validtime = ntohl(po->nd_opt_pi_valid_time); preftime = ntohl(po->nd_opt_pi_preferred_time); plen = po->nd_opt_pi_prefix_len; if (!new_prefix) { /* * Check 2 hour rule on valid lifetime. * Follows: RFC 2462 * If we advertised this prefix ourselves we skip * these checks. They are also skipped if we did not * previously do addrconf on this prefix. */ recorded_validtime = pr->pr_ValidLifetime / MILLISEC; if (loopback || !(pr->pr_state & PR_AUTO) || validtime >= MIN_VALID_LIFETIME || /* LINTED - statement has no consequent */ validtime >= recorded_validtime) { /* OK */ } else if (recorded_validtime < MIN_VALID_LIFETIME && validtime < recorded_validtime) { /* Ignore the prefix */ (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); (void) inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, pbuf, sizeof (pbuf)); logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: " "too short valid lifetime %u stored %u " "- ignored\n", pbuf, plen, abuf, pi->pi_name, validtime, recorded_validtime); return (_B_TRUE); } else { /* * If the router clock runs slower than the * host by 1 second over 2 hours then this * test will set the lifetime back to 2 hours * once i.e. a lifetime decrementing in * realtime might cause the prefix to live an * extra 2 hours on the host. */ (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); (void) inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, pbuf, sizeof (pbuf)); logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: " "valid time %u stored %u rounded up " "to %u\n", pbuf, plen, abuf, pi->pi_name, validtime, recorded_validtime, MIN_VALID_LIFETIME); validtime = MIN_VALID_LIFETIME; } } /* * For RFC3041 addresses, need to take token lifetime * into account, too. */ if (pr->pr_flags & IFF_TEMPORARY) { uint_t cur_tpreftime = pi->pi_TmpPreferredLifetime - pi->pi_TmpDesyncFactor; if (new_prefix) { validtime = MIN(validtime, pi->pi_TmpValidLifetime); preftime = MIN(preftime, cur_tpreftime); } else { uint_t cur_vexp, cur_pexp, curtime; curtime = getcurrenttime() / MILLISEC; cur_vexp = pr->pr_CreateTime + pi->pi_TmpValidLifetime; cur_pexp = pr->pr_CreateTime + cur_tpreftime; if (curtime > cur_vexp) validtime = 0; else if ((curtime + validtime) > cur_vexp) validtime = cur_vexp - curtime; /* * If this is an existing address which was deprecated * because of a bad token, we don't want to update its * preferred lifetime! */ if ((pr->pr_PreferredLifetime == 0) && !token_equal(pr->pr_address, pi->pi_tmp_token, TMP_TOKEN_BITS)) preftime = 0; else if (curtime > cur_pexp) preftime = 0; else if ((curtime + preftime) > cur_pexp) preftime = cur_pexp - curtime; } if ((preftime != 0) && (preftime <= pi->pi_TmpRegenAdvance)) { (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); (void) inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, pbuf, sizeof (pbuf)); logmsg(LOG_WARNING, "prefix opt %s/%u from %s on %s: " "preferred lifetime(%d) <= TmpRegenAdvance(%d)\n", pbuf, plen, abuf, pi->pi_name, preftime, pi->pi_TmpRegenAdvance); if (new_prefix) { prefix_update_ipadm_addrobj(pr, _B_FALSE); prefix_delete(pr); } return (_B_TRUE); } } if (debug & D_TMP) logmsg(LOG_DEBUG, "calculated lifetimes(%s, 0x%llx): v %d, " "p %d\n", pr->pr_name, pr->pr_flags, validtime, preftime); if (!(pr->pr_state & PR_AUTO)) { int i, tokenlen; in6_addr_t *token; /* * Form a new local address if the lengths match. */ if (pr->pr_flags & IFF_TEMPORARY) { if (IN6_IS_ADDR_UNSPECIFIED(&pi->pi_tmp_token)) { if (!tmptoken_create(pi)) { prefix_delete(pr); return (_B_TRUE); } } tokenlen = TMP_TOKEN_BITS; token = &pi->pi_tmp_token; } else { tokenlen = pi->pi_token_length; token = &pi->pi_token; } if (pr->pr_prefix_len + tokenlen != IPV6_ABITS) { (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); (void) inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, pbuf, sizeof (pbuf)); logmsg(LOG_INFO, "prefix option %s/%u from %s on %s: " "mismatched length %d token length %d\n", pbuf, plen, abuf, pi->pi_name, pr->pr_prefix_len, tokenlen); return (_B_TRUE); } for (i = 0; i < 16; i++) { /* * prefix_create ensures that pr_prefix has all-zero * bits after prefixlen. */ pr->pr_address.s6_addr[i] = pr->pr_prefix.s6_addr[i] | token->s6_addr[i]; } /* * Check if any other physical interface has the same * address configured already */ if ((other_pr = prefix_lookup_addr_match(pr)) != NULL) { /* * Delete this prefix structure as kernel * does not allow duplicated addresses */ logmsg(LOG_ERR, "incoming_prefix_addrconf_process: " "Duplicate prefix %s received on interface %s\n", inet_ntop(AF_INET6, &po->nd_opt_pi_prefix, abuf, sizeof (abuf)), pi->pi_name); logmsg(LOG_ERR, "incoming_prefix_addrconf_process: " "Prefix already exists in interface %s\n", other_pr->pr_physical->pi_name); if (new_prefix) { prefix_update_ipadm_addrobj(pr, _B_FALSE); prefix_delete(pr); return (_B_FALSE); } /* Ignore for addrconf purposes */ validtime = preftime = 0; } if ((pr->pr_flags & IFF_TEMPORARY) && new_prefix) { pr->pr_CreateTime = getcurrenttime() / MILLISEC; if (debug & D_TMP) logmsg(LOG_DEBUG, "created tmp addr(%s v %d p %d)\n", pr->pr_name, validtime, preftime); } } if (validtime != 0) pr->pr_state |= PR_AUTO; else pr->pr_state &= ~(PR_AUTO|PR_DEPRECATED); if (preftime != 0 || !(pr->pr_state & PR_AUTO)) pr->pr_state &= ~PR_DEPRECATED; else pr->pr_state |= PR_DEPRECATED; /* * Convert from seconds to milliseconds avoiding overflow. * If the lifetime in the packet is e.g. PREFIX_INFINITY - 1 * (4 billion seconds - about 130 years) we will in fact time * out the prefix after 4 billion milliseconds - 46 days). * Thus the longest lifetime (apart from infinity) is 46 days. * Note that this ensures that PREFIX_INFINITY still means "forever". */ if (validtime >= PREFIX_INFINITY / MILLISEC) pr->pr_ValidLifetime = PREFIX_INFINITY - 1; else pr->pr_ValidLifetime = validtime * MILLISEC; if (preftime >= PREFIX_INFINITY / MILLISEC) pr->pr_PreferredLifetime = PREFIX_INFINITY - 1; else pr->pr_PreferredLifetime = preftime * MILLISEC; pr->pr_AutonomousFlag = _B_TRUE; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "incoming_prefix_addrconf_process(%s, %s/%u) " "valid %u pref %u\n", pr->pr_physical->pi_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, pr->pr_ValidLifetime, pr->pr_PreferredLifetime); } if (pr->pr_state & PR_AUTO) { /* Take the min of the two timeouts by calling it twice */ if (pr->pr_ValidLifetime != 0) timer_schedule(pr->pr_ValidLifetime); if (pr->pr_PreferredLifetime != 0) timer_schedule(pr->pr_PreferredLifetime); } if (pr->pr_kernel_state != pr->pr_state) { /* Log a message when an addrconf prefix goes away */ if ((pr->pr_kernel_state & PR_AUTO) && !(pr->pr_state & PR_AUTO)) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_WARNING, "Address removed due to zero " "valid lifetime %s\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf))); } prefix_update_k(pr); } return (_B_TRUE); } /* * Process an MTU option received in a router advertisement. */ static void incoming_mtu_opt(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from) { struct nd_opt_mtu *mo = (struct nd_opt_mtu *)opt; struct lifreq lifr; uint32_t mtu; if (8 * mo->nd_opt_mtu_len != sizeof (*mo)) { char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "mtu option from %s on %s wrong size " "(%d bytes)\n", abuf, pi->pi_name, 8 * (int)mo->nd_opt_mtu_len); return; } mtu = ntohl(mo->nd_opt_mtu_mtu); if (pi->pi_LinkMTU == mtu) return; /* No change */ if (mtu > pi->pi_mtu) { /* Can't exceed physical MTU */ char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "mtu option from %s on %s too large " "MTU %d - %d\n", abuf, pi->pi_name, mtu, pi->pi_mtu); return; } if (mtu < IPV6_MIN_MTU) { char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "mtu option from %s on %s too small " "MTU (%d)\n", abuf, pi->pi_name, mtu); return; } pi->pi_LinkMTU = mtu; bzero(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_ifinfo.lir_maxmtu = pi->pi_LinkMTU; if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "incoming_mtu_opt: SIOCSLIFLNKINFO"); return; } } /* * Process a source link-layer address option received in a router * advertisement or solicitation. */ static void incoming_lla_opt(struct phyint *pi, uchar_t *opt, struct sockaddr_in6 *from, int isrouter) { struct nd_opt_lla *lo = (struct nd_opt_lla *)opt; struct lifreq lifr; struct sockaddr_in6 *sin6; int max_content_len; /* * Get our link-layer address length. We may not have one, in which * case we can just bail. */ if (phyint_get_lla(pi, &lifr) != 0) return; /* * Can't remove padding since it is link type specific. * However, we check against the length of our link-layer address. * Note: assumes that all links have a fixed length address. */ max_content_len = lo->nd_opt_lla_len * 8 - sizeof (struct nd_opt_hdr); if (max_content_len < lifr.lifr_nd.lnr_hdw_len || (max_content_len >= 8 && max_content_len - 7 > lifr.lifr_nd.lnr_hdw_len)) { char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "lla option from %s on %s too long with bad " "physaddr length (%d vs. %d bytes)\n", abuf, pi->pi_name, max_content_len, lifr.lifr_nd.lnr_hdw_len); return; } bcopy(lo->nd_opt_lla_hdw_addr, lifr.lifr_nd.lnr_hdw_addr, lifr.lifr_nd.lnr_hdw_len); sin6 = (struct sockaddr_in6 *)&lifr.lifr_nd.lnr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = from->sin6_addr; /* * Set IsRouter flag if RA; clear if RS. */ lifr.lifr_nd.lnr_state_create = ND_STALE; lifr.lifr_nd.lnr_state_same_lla = ND_UNCHANGED; lifr.lifr_nd.lnr_state_diff_lla = ND_STALE; lifr.lifr_nd.lnr_flags = isrouter; (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); if (ioctl(pi->pi_sock, SIOCLIFSETND, (char *)&lifr) < 0) { logperror_pi(pi, "incoming_lla_opt: SIOCLIFSETND"); return; } } /* * Verify the content of the received router advertisement against our * own configuration as specified in RFC 2461. */ static void verify_ra_consistency(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from) { char frombuf[INET6_ADDRSTRLEN]; struct nd_opt_hdr *opt; int optlen; uint_t reachable, retrans; boolean_t pktflag, myflag; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, frombuf, sizeof (frombuf)); if (ra->nd_ra_curhoplimit != 0 && pi->pi_AdvCurHopLimit != 0 && ra->nd_ra_curhoplimit != pi->pi_AdvCurHopLimit) { logmsg(LOG_INFO, "RA from %s on %s inconsistent cur hop " "limit:\n\treceived %d configuration %d\n", frombuf, pi->pi_name, ra->nd_ra_curhoplimit, pi->pi_AdvCurHopLimit); } reachable = ntohl(ra->nd_ra_reachable); if (reachable != 0 && pi->pi_AdvReachableTime != 0 && reachable != pi->pi_AdvReachableTime) { logmsg(LOG_INFO, "RA from %s on %s inconsistent reachable " "time:\n\treceived %d configuration %d\n", frombuf, pi->pi_name, reachable, pi->pi_AdvReachableTime); } retrans = ntohl(ra->nd_ra_retransmit); if (retrans != 0 && pi->pi_AdvRetransTimer != 0 && retrans != pi->pi_AdvRetransTimer) { logmsg(LOG_INFO, "RA from %s on %s inconsistent retransmit " "timer:\n\treceived %d configuration %d\n", frombuf, pi->pi_name, retrans, pi->pi_AdvRetransTimer); } pktflag = ((ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) != 0); myflag = (pi->pi_AdvManagedFlag != 0); if (pktflag != myflag) { logmsg(LOG_INFO, "RA from %s on %s inconsistent managed " "flag:\n\treceived %s configuration %s\n", frombuf, pi->pi_name, (pktflag ? "ON" : "OFF"), (myflag ? "ON" : "OFF")); } pktflag = ((ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) != 0); myflag = (pi->pi_AdvOtherConfigFlag != 0); if (pktflag != myflag) { logmsg(LOG_INFO, "RA from %s on %s inconsistent other config " "flag:\n\treceived %s configuration %s\n", frombuf, pi->pi_name, (pktflag ? "ON" : "OFF"), (myflag ? "ON" : "OFF")); } /* Process any options */ len -= sizeof (struct nd_router_advert); opt = (struct nd_opt_hdr *)&ra[1]; while (len >= sizeof (struct nd_opt_hdr)) { optlen = opt->nd_opt_len * 8; switch (opt->nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: verify_prefix_opt(pi, (uchar_t *)opt, frombuf); break; case ND_OPT_MTU: verify_mtu_opt(pi, (uchar_t *)opt, frombuf); break; default: break; } opt = (struct nd_opt_hdr *)((char *)opt + optlen); len -= optlen; } } /* * Verify that the lifetimes and onlink/auto flags are consistent * with our settings. */ static void verify_prefix_opt(struct phyint *pi, uchar_t *opt, char *frombuf) { struct nd_opt_prefix_info *po = (struct nd_opt_prefix_info *)opt; int plen; struct adv_prefix *adv_pr; uint32_t validtime, preftime; char prefixbuf[INET6_ADDRSTRLEN]; int pktflag, myflag; if (8 * po->nd_opt_pi_len != sizeof (*po)) { logmsg(LOG_INFO, "RA prefix option from %s on %s wrong size " "(%d bytes)\n", frombuf, pi->pi_name, 8 * (int)po->nd_opt_pi_len); return; } if (IN6_IS_ADDR_LINKLOCAL(&po->nd_opt_pi_prefix)) { logmsg(LOG_INFO, "RA from %s on %s contains link-local " "prefix - ignored\n", frombuf, pi->pi_name); return; } plen = po->nd_opt_pi_prefix_len; adv_pr = adv_prefix_lookup(pi, po->nd_opt_pi_prefix, plen); if (adv_pr == NULL) return; /* Ignore prefixes which we do not advertise */ if (!adv_pr->adv_pr_AdvAutonomousFlag && !adv_pr->adv_pr_AdvOnLinkFlag) return; (void) inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, prefixbuf, sizeof (prefixbuf)); pktflag = ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) != 0); myflag = (adv_pr->adv_pr_AdvAutonomousFlag != 0); if (pktflag != myflag) { logmsg(LOG_INFO, "RA from %s on %s inconsistent autonomous flag for \n\t" "prefix %s/%u: received %s configuration %s\n", frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, (pktflag ? "ON" : "OFF"), (myflag ? "ON" : "OFF")); } pktflag = ((po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) != 0); myflag = (adv_pr->adv_pr_AdvOnLinkFlag != 0); if (pktflag != myflag) { logmsg(LOG_INFO, "RA from %s on %s inconsistent on link flag " "for \n\tprefix %s/%u: received %s configuration %s\n", frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, (pktflag ? "ON" : "OFF"), (myflag ? "ON" : "OFF")); } validtime = ntohl(po->nd_opt_pi_valid_time); preftime = ntohl(po->nd_opt_pi_preferred_time); /* * Take into account variation for lifetimes decrementing * in real time. Allow +/- 10 percent and +/- 10 seconds. */ #define LOWER_LIMIT(val) ((val) - (val)/10 - 10) #define UPPER_LIMIT(val) ((val) + (val)/10 + 10) if (adv_pr->adv_pr_AdvValidRealTime) { if (adv_pr->adv_pr_AdvValidExpiration > 0 && (validtime < LOWER_LIMIT(adv_pr->adv_pr_AdvValidExpiration) || validtime > UPPER_LIMIT(adv_pr->adv_pr_AdvValidExpiration))) { logmsg(LOG_INFO, "RA from %s on %s inconsistent valid " "lifetime for\n\tprefix %s/%u: received %d " "configuration %d\n", frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, validtime, adv_pr->adv_pr_AdvValidExpiration); } } else { if (validtime != adv_pr->adv_pr_AdvValidLifetime) { logmsg(LOG_INFO, "RA from %s on %s inconsistent valid " "lifetime for\n\tprefix %s/%u: received %d " "configuration %d\n", frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, validtime, adv_pr->adv_pr_AdvValidLifetime); } } if (adv_pr->adv_pr_AdvPreferredRealTime) { if (adv_pr->adv_pr_AdvPreferredExpiration > 0 && (preftime < LOWER_LIMIT(adv_pr->adv_pr_AdvPreferredExpiration) || preftime > UPPER_LIMIT(adv_pr->adv_pr_AdvPreferredExpiration))) { logmsg(LOG_INFO, "RA from %s on %s inconsistent " "preferred lifetime for\n\tprefix %s/%u: " "received %d configuration %d\n", frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, preftime, adv_pr->adv_pr_AdvPreferredExpiration); } } else { if (preftime != adv_pr->adv_pr_AdvPreferredLifetime) { logmsg(LOG_INFO, "RA from %s on %s inconsistent " "preferred lifetime for\n\tprefix %s/%u: " "received %d configuration %d\n", frombuf, pi->pi_name, prefixbuf, adv_pr->adv_pr_prefix_len, preftime, adv_pr->adv_pr_AdvPreferredLifetime); } } } /* * Verify the received MTU against our own configuration. */ static void verify_mtu_opt(struct phyint *pi, uchar_t *opt, char *frombuf) { struct nd_opt_mtu *mo = (struct nd_opt_mtu *)opt; uint32_t mtu; if (8 * mo->nd_opt_mtu_len != sizeof (*mo)) { logmsg(LOG_INFO, "mtu option from %s on %s wrong size " "(%d bytes)\n", frombuf, pi->pi_name, 8 * (int)mo->nd_opt_mtu_len); return; } mtu = ntohl(mo->nd_opt_mtu_mtu); if (pi->pi_AdvLinkMTU != 0 && pi->pi_AdvLinkMTU != mtu) { logmsg(LOG_INFO, "RA from %s on %s inconsistent MTU: " "received %d configuration %d\n", frombuf, pi->pi_name, mtu, pi->pi_AdvLinkMTU); } } /* * Verify that all options have a non-zero length and that * the options fit within the total length of the packet (optlen). */ static boolean_t verify_opt_len(struct nd_opt_hdr *opt, int optlen, struct phyint *pi, struct sockaddr_in6 *from) { while (optlen > 0) { if (opt->nd_opt_len == 0) { char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "Zero length option type 0x%x " "from %s on %s\n", opt->nd_opt_type, abuf, pi->pi_name); return (_B_FALSE); } optlen -= 8 * opt->nd_opt_len; if (optlen < 0) { char abuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "Too large option: type 0x%x len %u " "from %s on %s\n", opt->nd_opt_type, opt->nd_opt_len, abuf, pi->pi_name); return (_B_FALSE); } opt = (struct nd_opt_hdr *)((char *)opt + 8 * opt->nd_opt_len); } return (_B_TRUE); } /* * Update IsRouter Flag for Host turning into a router or vice-versa. */ static void update_ra_flag(const struct phyint *pi, const struct sockaddr_in6 *from, int isrouter) { struct lifreq lifr; char abuf[INET6_ADDRSTRLEN]; struct sockaddr_in6 *sin6; /* check if valid flag is being set */ if ((isrouter != NDF_ISROUTER_ON) && (isrouter != NDF_ISROUTER_OFF)) { logmsg(LOG_ERR, "update_ra_flag: Invalid IsRouter " "flag %d\n", isrouter); return; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_nd.lnr_addr; bzero(sin6, sizeof (*sin6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = from->sin6_addr; (void) strlcpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); if (ioctl(pi->pi_sock, SIOCLIFGETND, (char *)&lifr) < 0) { if (errno == ESRCH) { if (debug & D_IFSCAN) { logmsg(LOG_DEBUG, "update_ra_flag: SIOCLIFGETND: nce doesn't exist, not setting IFF_ROUTER\n"); } } else { logperror_pi(pi, "update_ra_flag: SIOCLIFGETND"); } } else { /* * The lif_nd_req structure has three state values to be used * when changing/updating nces : * lnr_state_create, lnr_state_same_lla, and lnr_state_diff_lla. * * In this case, we're updating an nce, without changing lla; * so we set lnr_state_same_lla to ND_UNCHANGED, indicating that * nce's state should not be affected by our flag change. * * The kernel implementation also expects the lnr_state_create * field be always set, before processing ioctl request for NCE * update. * We use the state as STALE, while addressing the possibility * of NCE deletion when ioctl with SIOCLIFGETND argument * in earlier step is returned - further in such case we don't * want to re-create the entry in the reachable state. */ lifr.lifr_nd.lnr_state_create = ND_STALE; lifr.lifr_nd.lnr_state_same_lla = ND_UNCHANGED; lifr.lifr_nd.lnr_flags = isrouter; if ((ioctl(pi->pi_sock, SIOCLIFSETND, (char *)&lifr)) < 0) { logperror_pi(pi, "update_ra_flag: SIOCLIFSETND"); } else { (void) inet_ntop(AF_INET6, (void *)&from->sin6_addr, abuf, sizeof (abuf)); logmsg(LOG_INFO, "update_ra_flag: IsRouter flag " "updated for %s\n", abuf); } } } #!/sbin/sh # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" . /lib/svc/share/smf_include.sh . /lib/svc/share/routing_include.sh smf_configure_ip || exit $SMF_EXIT_OK daemon_args=`get_daemon_args $SMF_FMRI` options="adtf:" # # Handle upgrade - routing/daemon-args property must be mapped to properties # in routeadm property group. Note that the SMF-incompatible -t option is not # supported, since it requires that in.ndpd run in the foreground. # if [ -n "$daemon_args" ]; then set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "a" stateless_addr_conf false true set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "d" debug true false set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$options" "f" config_file clear_daemon_args $SMF_FMRI fi # # Assemble arguments to daemon from properties # args="`get_daemon_option_from_boolean_property $SMF_FMRI stateless_addr_conf \ a false`" args="$args`get_daemon_option_from_boolean_property $SMF_FMRI debug d true`" if [ -n "$args" ]; then args="-${args}" fi args="$args `get_daemon_option_from_property $SMF_FMRI config_file f`" /usr/lib/inet/in.ndpd $args [ "$?" = 0 ] || exit $SMF_EXIT_ERR_FATAL exit $SMF_EXIT_OK /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * * Copyright 2024 Oxide Computer Company */ #include "defs.h" #include "tables.h" #include #include struct phyint *phyints = NULL; int num_of_phyints = 0; static void phyint_print(struct phyint *pi); static void phyint_insert(struct phyint *pi); static boolean_t tmptoken_isvalid(struct in6_addr *token); static void prefix_print(struct prefix *pr); static void prefix_insert(struct phyint *pi, struct prefix *pr); static char *prefix_print_state(int state, char *buf, int buflen); static void prefix_set(struct in6_addr *prefix, struct in6_addr addr, int bits); static void adv_prefix_print(struct adv_prefix *adv_pr); static void adv_prefix_insert(struct phyint *pi, struct adv_prefix *adv_pr); static void adv_prefix_delete(struct adv_prefix *adv_pr); static void router_print(struct router *dr); static void router_insert(struct phyint *pi, struct router *dr); static void router_delete(struct router *dr); static void router_add_k(struct router *dr); static void router_delete_k(struct router *dr); static int rtmseq; /* rtm_seq sequence number */ /* 1 week in ms */ #define NDP_PREFIX_DEFAULT_LIFETIME (7*24*60*60*1000) struct phyint * phyint_lookup(char *name) { struct phyint *pi; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_lookup(%s)\n", name); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (strcmp(pi->pi_name, name) == 0) break; } return (pi); } struct phyint * phyint_lookup_on_index(uint_t ifindex) { struct phyint *pi; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_lookup_on_index(%d)\n", ifindex); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (pi->pi_index == ifindex) break; } return (pi); } struct phyint * phyint_create(char *name) { struct phyint *pi; int i; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_create(%s)\n", name); pi = (struct phyint *)calloc(sizeof (struct phyint), 1); if (pi == NULL) { logmsg(LOG_ERR, "phyint_create: out of memory\n"); return (NULL); } (void) strncpy(pi->pi_name, name, sizeof (pi->pi_name)); pi->pi_name[sizeof (pi->pi_name) - 1] = '\0'; /* * Copy the defaults from the defaults array. * Do not copy the cf_notdefault fields since these have not * been explicitly set for the phyint. */ for (i = 0; i < I_IFSIZE; i++) pi->pi_config[i].cf_value = ifdefaults[i].cf_value; /* * TmpDesyncFactor is used to desynchronize temporary token * generation among systems; the actual preferred lifetime value * of a temporary address will be (TmpPreferredLifetime - * TmpDesyncFactor). It's a random value, with a user-configurable * maximum value. The value is constant throughout the lifetime * of the in.ndpd process, but can change if the daemon is restarted, * per RFC3041. */ if (pi->pi_TmpMaxDesyncFactor != 0) { time_t seed = time(NULL); srand((uint_t)seed); pi->pi_TmpDesyncFactor = rand() % pi->pi_TmpMaxDesyncFactor; /* we actually want [1,max], not [0,(max-1)] */ pi->pi_TmpDesyncFactor++; } pi->pi_TmpRegenCountdown = TIMER_INFINITY; pi->pi_sock = -1; pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; pi->pi_autoconf = _B_TRUE; pi->pi_default_token = _B_TRUE; if (phyint_init_from_k(pi) == -1) { free(pi); return (NULL); } phyint_insert(pi); if (pi->pi_sock != -1) { if (poll_add(pi->pi_sock) == -1) { phyint_delete(pi); return (NULL); } } return (pi); } /* Insert in linked list */ static void phyint_insert(struct phyint *pi) { /* Insert in list */ pi->pi_next = phyints; pi->pi_prev = NULL; if (phyints) phyints->pi_prev = pi; phyints = pi; num_of_phyints++; } /* * Initialize both the phyint data structure and the pi_sock for * sending and receving on the interface. * Extract information from the kernel (if present) and set pi_kernel_state. */ int phyint_init_from_k(struct phyint *pi) { struct ipv6_mreq v6mcastr; struct lifreq lifr; int fd; int save_errno; boolean_t newsock; uint_t ttl; struct sockaddr_in6 *sin6; if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_init_from_k(%s)\n", pi->pi_name); start_over: if (pi->pi_sock < 0) { pi->pi_sock = socket(AF_INET6, SOCK_RAW, IPPROTO_ICMPV6); if (pi->pi_sock < 0) { logperror_pi(pi, "phyint_init_from_k: socket"); return (-1); } newsock = _B_TRUE; } else { newsock = _B_FALSE; } fd = pi->pi_sock; (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(fd, SIOCGLIFINDEX, (char *)&lifr) < 0) { if (errno == ENXIO) { if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): " "not exist\n", pi->pi_name); } return (0); } logperror_pi(pi, "phyint_init_from_k: SIOCGLIFINDEX"); goto error; } if (!newsock && (pi->pi_index != lifr.lifr_index)) { /* * Interface has been re-plumbed, lets open a new socket. * This situation can occur if plumb/unplumb are happening * quite frequently. */ phyint_cleanup(pi); goto start_over; } pi->pi_index = lifr.lifr_index; if (ioctl(fd, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: ioctl (get flags)"); goto error; } pi->pi_flags = lifr.lifr_flags; /* * If the link local interface is not up yet or it's IFF_UP and the * IFF_NOLOCAL flag is set, then ignore the interface. */ if (!(pi->pi_flags & IFF_UP) || (pi->pi_flags & IFF_NOLOCAL)) { if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): " "IFF_NOLOCAL or not IFF_UP\n", pi->pi_name); } return (0); } pi->pi_kernel_state |= PI_PRESENT; if (ioctl(fd, SIOCGLIFMTU, (caddr_t)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: ioctl (get mtu)"); goto error; } pi->pi_mtu = lifr.lifr_mtu; if (ioctl(fd, SIOCGLIFADDR, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFADDR"); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; pi->pi_ifaddr = sin6->sin6_addr; if (pi->pi_autoconf && pi->pi_default_token) { if (ioctl(fd, SIOCGLIFTOKEN, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFTOKEN"); goto error; } /* Ignore interface if the token is all zeros */ sin6 = (struct sockaddr_in6 *)&lifr.lifr_token; if (IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr)) { logmsg(LOG_ERR, "ignoring interface %s: zero token\n", pi->pi_name); goto error; } pi->pi_token = sin6->sin6_addr; pi->pi_token_length = lifr.lifr_addrlen; } /* * Guess a remote token for POINTOPOINT by looking at * the link-local destination address. */ if (pi->pi_flags & IFF_POINTOPOINT) { if (ioctl(fd, SIOCGLIFDSTADDR, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFDSTADDR"); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; if (sin6->sin6_family != AF_INET6 || IN6_IS_ADDR_UNSPECIFIED(&sin6->sin6_addr) || !IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) { pi->pi_dst_token = in6addr_any; } else { pi->pi_dst_token = sin6->sin6_addr; /* Clear link-local prefix (first 10 bits) */ pi->pi_dst_token.s6_addr[0] = 0; pi->pi_dst_token.s6_addr[1] &= 0x3f; } } else { pi->pi_dst_token = in6addr_any; } if (newsock) { icmp6_filter_t filter; int on = 1; /* Set default values */ pi->pi_LinkMTU = pi->pi_mtu; pi->pi_CurHopLimit = 0; pi->pi_BaseReachableTime = ND_REACHABLE_TIME; phyint_reach_random(pi, _B_FALSE); pi->pi_RetransTimer = ND_RETRANS_TIMER; /* Setup socket for transmission and reception */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_BOUND_IF, (char *)&pi->pi_index, sizeof (pi->pi_index)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_BOUND_IF"); goto error; } ttl = IPV6_MAX_HOPS; if (setsockopt(fd, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&ttl, sizeof (ttl)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_UNICAST_HOPS"); goto error; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&ttl, sizeof (ttl)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_MULTICAST_HOPS"); goto error; } v6mcastr.ipv6mr_multiaddr = all_nodes_mcast; v6mcastr.ipv6mr_interface = pi->pi_index; if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&v6mcastr, sizeof (v6mcastr)) < 0) { /* * One benign reason IPV6_JOIN_GROUP could fail is * when `pi' has been placed into an IPMP group and we * haven't yet processed the routing socket message * informing us of its disappearance. As such, if * it's now in a group, don't print an error. */ save_errno = errno; (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1 || lifr.lifr_groupname[0] == '\0') { errno = save_errno; logperror_pi(pi, "phyint_init_from_k: " "setsockopt IPV6_JOIN_GROUP"); } goto error; } pi->pi_state |= PI_JOINED_ALLNODES; pi->pi_kernel_state |= PI_JOINED_ALLNODES; /* * Filter out so that we only receive router advertisements and * router solicitations. */ ICMP6_FILTER_SETBLOCKALL(&filter); ICMP6_FILTER_SETPASS(ND_ROUTER_SOLICIT, &filter); ICMP6_FILTER_SETPASS(ND_ROUTER_ADVERT, &filter); if (setsockopt(fd, IPPROTO_ICMPV6, ICMP6_FILTER, (char *)&filter, sizeof (filter)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "ICMP6_FILTER"); goto error; } /* Enable receipt of ancillary data */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, (char *)&on, sizeof (on)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_RECVHOPLIMIT"); goto error; } if (setsockopt(fd, IPPROTO_IPV6, IPV6_RECVRTHDR, (char *)&on, sizeof (on)) < 0) { logperror_pi(pi, "phyint_init_from_k: setsockopt " "IPV6_RECVRTHDR"); goto error; } } if (pi->pi_AdvSendAdvertisements && !(pi->pi_kernel_state & PI_JOINED_ALLROUTERS)) { v6mcastr.ipv6mr_multiaddr = all_routers_mcast; v6mcastr.ipv6mr_interface = pi->pi_index; if (setsockopt(fd, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&v6mcastr, sizeof (v6mcastr)) < 0) { /* * See IPV6_JOIN_GROUP comment above. */ save_errno = errno; (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); if (ioctl(fd, SIOCGLIFGROUPNAME, &lifr) == -1 || lifr.lifr_groupname[0] == '\0') { errno = save_errno; logperror_pi(pi, "phyint_init_from_k: " "setsockopt IPV6_JOIN_GROUP"); } goto error; } pi->pi_state |= PI_JOINED_ALLROUTERS; pi->pi_kernel_state |= PI_JOINED_ALLROUTERS; } /* * If not already set, set the IFF_ROUTER interface flag based on * AdvSendAdvertisements. Note that this will also enable IPv6 * forwarding on the interface. We don't clear IFF_ROUTER if we're * not advertising on an interface, because we could still be * forwarding on those interfaces. */ (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(fd, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCGLIFFLAGS"); goto error; } if (!(lifr.lifr_flags & IFF_ROUTER) && pi->pi_AdvSendAdvertisements) { lifr.lifr_flags |= IFF_ROUTER; if (ioctl(fd, SIOCSLIFFLAGS, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCSLIFFLAGS"); goto error; } pi->pi_flags = lifr.lifr_flags; } /* Set linkinfo parameters */ (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; lifr.lifr_ifinfo.lir_maxhops = pi->pi_CurHopLimit; lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; lifr.lifr_ifinfo.lir_reachretrans = pi->pi_RetransTimer; /* Setting maxmtu to 0 means that we're leaving the MTU alone */ lifr.lifr_ifinfo.lir_maxmtu = 0; if (ioctl(fd, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_init_from_k: SIOCSLIFLNKINFO"); goto error; } if (debug & D_PHYINT) { logmsg(LOG_DEBUG, "phyint_init_from_k(%s): done\n", pi->pi_name); } return (0); error: /* Pretend the interface does not exist in the kernel */ pi->pi_kernel_state &= ~PI_PRESENT; if (newsock) { (void) close(pi->pi_sock); pi->pi_sock = -1; } return (-1); } /* * Delete (unlink and free). * Handles delete of things that have not yet been inserted in the list. */ void phyint_delete(struct phyint *pi) { if (debug & D_PHYINT) logmsg(LOG_DEBUG, "phyint_delete(%s)\n", pi->pi_name); assert(num_of_phyints > 0); while (pi->pi_router_list) router_delete(pi->pi_router_list); while (pi->pi_prefix_list) { prefix_update_ipadm_addrobj(pi->pi_prefix_list, _B_FALSE); prefix_delete(pi->pi_prefix_list); } while (pi->pi_adv_prefix_list) adv_prefix_delete(pi->pi_adv_prefix_list); if (pi->pi_sock != -1) { (void) poll_remove(pi->pi_sock); if (close(pi->pi_sock) < 0) { logperror_pi(pi, "phyint_delete: close"); } pi->pi_sock = -1; } if (pi->pi_prev == NULL) { if (phyints == pi) phyints = pi->pi_next; } else { pi->pi_prev->pi_next = pi->pi_next; } if (pi->pi_next != NULL) pi->pi_next->pi_prev = pi->pi_prev; pi->pi_next = pi->pi_prev = NULL; free(pi); num_of_phyints--; } /* * Called with the number of milliseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event * for the phyint itself (excluding prefixes and routers). * Returns TIMER_INFINITY for "never". */ uint_t phyint_timer(struct phyint *pi, uint_t elapsed) { uint_t next = TIMER_INFINITY; if (pi->pi_AdvSendAdvertisements) { if (pi->pi_adv_state != NO_ADV) { int old_state = pi->pi_adv_state; if (debug & (D_STATE|D_PHYINT)) { logmsg(LOG_DEBUG, "phyint_timer ADV(%s) " "state %d\n", pi->pi_name, (int)old_state); } next = advertise_event(pi, ADV_TIMER, elapsed); if (debug & D_STATE) { logmsg(LOG_DEBUG, "phyint_timer ADV(%s) " "state %d -> %d\n", pi->pi_name, (int)old_state, (int)pi->pi_adv_state); } } } else { if (pi->pi_sol_state != NO_SOLICIT) { int old_state = pi->pi_sol_state; if (debug & (D_STATE|D_PHYINT)) { logmsg(LOG_DEBUG, "phyint_timer SOL(%s) " "state %d\n", pi->pi_name, (int)old_state); } next = solicit_event(pi, SOL_TIMER, elapsed); if (debug & D_STATE) { logmsg(LOG_DEBUG, "phyint_timer SOL(%s) " "state %d -> %d\n", pi->pi_name, (int)old_state, (int)pi->pi_sol_state); } } } /* * If the phyint has been unplumbed, we don't want to call * phyint_reach_random. We will be in the NO_ADV or NO_SOLICIT state. */ if ((pi->pi_AdvSendAdvertisements && (pi->pi_adv_state != NO_ADV)) || (!pi->pi_AdvSendAdvertisements && (pi->pi_sol_state != NO_SOLICIT))) { pi->pi_reach_time_since_random += elapsed; if (pi->pi_reach_time_since_random >= MAX_REACH_RANDOM_INTERVAL) phyint_reach_random(pi, _B_TRUE); } return (next); } static void phyint_print(struct phyint *pi) { struct prefix *pr; struct adv_prefix *adv_pr; struct router *dr; char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "Phyint %s index %d state %x, kernel %x, " "num routers %d\n", pi->pi_name, pi->pi_index, pi->pi_state, pi->pi_kernel_state, pi->pi_num_k_routers); logmsg(LOG_DEBUG, "\taddress: %s flags %llx\n", inet_ntop(AF_INET6, (void *)&pi->pi_ifaddr, abuf, sizeof (abuf)), pi->pi_flags); logmsg(LOG_DEBUG, "\tsock %d mtu %d\n", pi->pi_sock, pi->pi_mtu); logmsg(LOG_DEBUG, "\ttoken: len %d %s\n", pi->pi_token_length, inet_ntop(AF_INET6, (void *)&pi->pi_token, abuf, sizeof (abuf))); if (pi->pi_TmpAddrsEnabled) { logmsg(LOG_DEBUG, "\ttmp_token: %s\n", inet_ntop(AF_INET6, (void *)&pi->pi_tmp_token, abuf, sizeof (abuf))); logmsg(LOG_DEBUG, "\ttmp config: pref %d valid %d " "maxdesync %d desync %d regen %d\n", pi->pi_TmpPreferredLifetime, pi->pi_TmpValidLifetime, pi->pi_TmpMaxDesyncFactor, pi->pi_TmpDesyncFactor, pi->pi_TmpRegenAdvance); } if (pi->pi_flags & IFF_POINTOPOINT) { logmsg(LOG_DEBUG, "\tdst_token: %s\n", inet_ntop(AF_INET6, (void *)&pi->pi_dst_token, abuf, sizeof (abuf))); } logmsg(LOG_DEBUG, "\tLinkMTU %d CurHopLimit %d " "BaseReachableTime %d\n\tReachableTime %d RetransTimer %d\n", pi->pi_LinkMTU, pi->pi_CurHopLimit, pi->pi_BaseReachableTime, pi->pi_ReachableTime, pi->pi_RetransTimer); if (!pi->pi_AdvSendAdvertisements) { /* Solicit state */ logmsg(LOG_DEBUG, "\tSOLICIT: time_left %d state %d count %d\n", pi->pi_sol_time_left, pi->pi_sol_state, pi->pi_sol_count); } else { /* Advertise state */ logmsg(LOG_DEBUG, "\tADVERT: time_left %d state %d count %d " "since last %d\n", pi->pi_adv_time_left, pi->pi_adv_state, pi->pi_adv_count, pi->pi_adv_time_since_sent); print_iflist(pi->pi_config); } for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) prefix_print(pr); for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { adv_prefix_print(adv_pr); } for (dr = pi->pi_router_list; dr != NULL; dr = dr->dr_next) router_print(dr); logmsg(LOG_DEBUG, "\n"); } /* * Store the LLA for the phyint `pi' `lifrp'. Returns 0 on success, or * -1 on failure. * * Note that we do not cache the hardware address since there's no reliable * mechanism to determine when it's become stale. */ int phyint_get_lla(struct phyint *pi, struct lifreq *lifrp) { struct sockaddr_in6 *sin6; /* If this phyint doesn't have a link-layer address, bail */ if (!(pi->pi_flags & IFF_MULTICAST) || (pi->pi_flags & IFF_POINTOPOINT)) { return (-1); } (void) strlcpy(lifrp->lifr_name, pi->pi_name, LIFNAMSIZ); sin6 = (struct sockaddr_in6 *)&(lifrp->lifr_nd.lnr_addr); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pi->pi_ifaddr; if (ioctl(pi->pi_sock, SIOCLIFGETND, lifrp) < 0) { /* * For IPMP interfaces, don't report ESRCH errors since that * merely indicates that there are no active interfaces in the * IPMP group (and thus there's no working hardware address), * and the packet will thus never make it out anyway. */ if (!(pi->pi_flags & IFF_IPMP) || errno != ESRCH) logperror_pi(pi, "phyint_get_lla: SIOCLIFGETND"); return (-1); } return (0); } /* * Randomize pi->pi_ReachableTime. * Done periodically when there are no RAs and at a maximum frequency when * RA's arrive. * Assumes that caller has determined that it is time to generate * a new random ReachableTime. */ void phyint_reach_random(struct phyint *pi, boolean_t set_needed) { struct lifreq lifr; pi->pi_ReachableTime = GET_RANDOM( (int)(ND_MIN_RANDOM_FACTOR * pi->pi_BaseReachableTime), (int)(ND_MAX_RANDOM_FACTOR * pi->pi_BaseReachableTime)); if (set_needed) { bzero(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pi->pi_name, LIFNAMSIZ); lifr.lifr_ifinfo.lir_reachtime = pi->pi_ReachableTime; if (ioctl(pi->pi_sock, SIOCSLIFLNKINFO, (char *)&lifr) < 0) { logperror_pi(pi, "phyint_reach_random: SIOCSLIFLNKINFO"); return; } } pi->pi_reach_time_since_random = 0; } /* * Validate a temporary token against a list of known bad values. * Currently assumes that token is 8 bytes long! Current known * bad values include 0, reserved anycast tokens (RFC 2526), tokens * used by ISATAP (draft-ietf-ngtrans-isatap-N), any token already * assigned to this interface, or any token for which the global * bit is set. * * Called by tmptoken_create(). * * Return _B_TRUE if token is valid (no match), _B_FALSE if not. */ static boolean_t tmptoken_isvalid(struct in6_addr *token) { struct phyint *pi; struct in6_addr mask; struct in6_addr isatap = { 0, 0, 0, 0, 0, 0, 0, 0, \ 0, 0, 0x5e, 0xfe, 0, 0, 0, 0 }; struct in6_addr anycast = { 0, 0, 0, 0, \ 0, 0, 0, 0, \ 0xfd, 0xff, 0xff, 0xff, \ 0xff, 0xff, 0xff, 0x80 }; if (IN6_IS_ADDR_UNSPECIFIED(token)) return (_B_FALSE); if (token->s6_addr[8] & 0x2) return (_B_FALSE); (void) memcpy(&mask, token, sizeof (mask)); mask._S6_un._S6_u32[3] = 0; if (IN6_ARE_ADDR_EQUAL(&isatap, token)) return (_B_FALSE); mask._S6_un._S6_u32[3] = token->_S6_un._S6_u32[3] & 0xffffff80; if (IN6_ARE_ADDR_EQUAL(&anycast, token)) return (_B_FALSE); for (pi = phyints; pi != NULL; pi = pi->pi_next) { if (((pi->pi_token_length == TMP_TOKEN_BITS) && IN6_ARE_ADDR_EQUAL(&pi->pi_token, token)) || IN6_ARE_ADDR_EQUAL(&pi->pi_tmp_token, token)) return (_B_FALSE); } /* none of our tests failed, must be a good one! */ return (_B_TRUE); } /* * Generate a temporary token and set up its timer * * Called from incoming_prefix_addrconf_process() (when token is first * needed) and from tmptoken_timer() (when current token expires). * * Returns _B_TRUE if a token was successfully generated, _B_FALSE if not. */ boolean_t tmptoken_create(struct phyint *pi) { int fd, i = 0, max_tries = 15; struct in6_addr token; uint32_t *tokenp = &(token._S6_un._S6_u32[2]); char buf[INET6_ADDRSTRLEN]; if ((fd = open("/dev/urandom", O_RDONLY)) == -1) { perror("open /dev/urandom"); goto no_token; } bzero((char *)&token, sizeof (token)); do { if (read(fd, (void *)tokenp, TMP_TOKEN_BYTES) == -1) { perror("read /dev/urandom"); (void) close(fd); goto no_token; } /* * Assume EUI-64 formatting, and thus 64-bit * token len; need to clear global bit. */ token.s6_addr[8] &= 0xfd; i++; } while (!tmptoken_isvalid(&token) && i < max_tries); (void) close(fd); if (i == max_tries) { no_token: logmsg(LOG_WARNING, "tmptoken_create(%s): failed to create " "token; disabling temporary addresses on %s\n", pi->pi_name, pi->pi_name); pi->pi_TmpAddrsEnabled = 0; return (_B_FALSE); } pi->pi_tmp_token = token; if (debug & D_TMP) logmsg(LOG_DEBUG, "tmptoken_create(%s): created temporary " "token %s\n", pi->pi_name, inet_ntop(AF_INET6, &pi->pi_tmp_token, buf, sizeof (buf))); pi->pi_TmpRegenCountdown = (pi->pi_TmpPreferredLifetime - pi->pi_TmpDesyncFactor - pi->pi_TmpRegenAdvance) * MILLISEC; if (pi->pi_TmpRegenCountdown != 0) timer_schedule(pi->pi_TmpRegenCountdown); return (_B_TRUE); } /* * Delete a temporary token. This is outside the normal timeout process, * so mark any existing addresses based on this token DEPRECATED and set * their preferred lifetime to 0. Don't tamper with valid lifetime, that * will be used to eventually remove the address. Also reset the current * pi_tmp_token value to 0. * * Called from incoming_prefix_addrconf_process() if DAD fails on a temp * addr. */ void tmptoken_delete(struct phyint *pi) { struct prefix *pr; for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (!(pr->pr_flags & IFF_TEMPORARY) || (pr->pr_flags & IFF_DEPRECATED) || (!token_equal(pr->pr_address, pi->pi_tmp_token, TMP_TOKEN_BITS))) { continue; } pr->pr_PreferredLifetime = 0; pr->pr_state |= PR_DEPRECATED; prefix_update_k(pr); } (void) memset(&pi->pi_tmp_token, 0, sizeof (pi->pi_tmp_token)); } /* * Called from run_timeouts() with the number of milliseconds elapsed * since the last call. Determines if any timeout event has occurred * and returns the number of milliseconds until the next timeout event * for the tmp token. Returns TIMER_INFINITY for "never". */ uint_t tmptoken_timer(struct phyint *pi, uint_t elapsed) { struct nd_opt_prefix_info opt; struct sockaddr_in6 sin6; struct prefix *pr, *newpr; if (debug & D_TMP) { logmsg(LOG_DEBUG, "tmptoken_timer(%s, %d) regencountdown %d\n", pi->pi_name, (int)elapsed, pi->pi_TmpRegenCountdown); } if (!pi->pi_TmpAddrsEnabled || (pi->pi_TmpRegenCountdown == TIMER_INFINITY)) return (TIMER_INFINITY); if (pi->pi_TmpRegenCountdown > elapsed) { pi->pi_TmpRegenCountdown -= elapsed; return (pi->pi_TmpRegenCountdown); } /* * Tmp token timer has expired. Start by generating a new token. * If we can't get a new token, tmp addrs are disabled on this * interface, so there's no need to continue, or to set a timer. */ if (!tmptoken_create(pi)) return (TIMER_INFINITY); /* * Now that we have a new token, walk the list of prefixes to * find which ones need a corresponding tmp addr generated. */ for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (!(pr->pr_state & PR_AUTO) || pr->pr_state & PR_STATIC || pr->pr_state & PR_DEPRECATED || pr->pr_flags & IFF_TEMPORARY) continue; newpr = prefix_create(pi, pr->pr_prefix, pr->pr_prefix_len, IFF_TEMPORARY); if (newpr == NULL) { char pbuf[INET6_ADDRSTRLEN]; char tbuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, &pr->pr_prefix, pbuf, sizeof (pbuf)); (void) inet_ntop(AF_INET6, &pi->pi_tmp_token, tbuf, sizeof (tbuf)); logmsg(LOG_ERR, "can't create new tmp addr " "(%s, %s, %s)\n", pi->pi_name, pbuf, tbuf); continue; } /* * We want to use incoming_prefix_*_process() functions to * set up the new tmp addr, so cobble together a prefix * info option struct based on the existing prefix to pass * in. The lifetimes will be based on the current time * remaining. * * The "from" param is only used for messages; pass in * ::0 for that. */ opt.nd_opt_pi_type = ND_OPT_PREFIX_INFORMATION; opt.nd_opt_pi_len = sizeof (opt) / 8; opt.nd_opt_pi_prefix_len = pr->pr_prefix_len; opt.nd_opt_pi_flags_reserved = ND_OPT_PI_FLAG_AUTO; opt.nd_opt_pi_valid_time = htonl(pr->pr_ValidLifetime / 1000); opt.nd_opt_pi_preferred_time = htonl(pr->pr_PreferredLifetime / 1000); if (pr->pr_state & PR_ONLINK) opt.nd_opt_pi_flags_reserved &= ND_OPT_PI_FLAG_ONLINK; opt.nd_opt_pi_prefix = pr->pr_prefix; (void) memset(&sin6, 0, sizeof (sin6)); if (!incoming_prefix_addrconf_process(pi, newpr, (uchar_t *)&opt, &sin6, _B_FALSE, _B_TRUE)) { char pbuf[INET6_ADDRSTRLEN]; char tbuf[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, &pr->pr_prefix, pbuf, sizeof (pbuf)); (void) inet_ntop(AF_INET6, &pi->pi_tmp_token, tbuf, sizeof (tbuf)); logmsg(LOG_ERR, "can't create new tmp addr " "(%s, %s, %s)\n", pi->pi_name, pbuf, tbuf); continue; } if (pr->pr_state & PR_ONLINK) { incoming_prefix_onlink_process(newpr, (uchar_t *)&opt); } } /* * appropriate timers were scheduled when * the token and addresses were created. */ return (TIMER_INFINITY); } /* * tlen specifies the token length in bits. Compares the lower * tlen bits of the two addresses provided and returns _B_TRUE if * they match, _B_FALSE if not. Also returns _B_FALSE for invalid * values of tlen. */ boolean_t token_equal(struct in6_addr t1, struct in6_addr t2, int tlen) { uchar_t mask; int j, abytes, tbytes, tbits; if (tlen < 0 || tlen > IPV6_ABITS) return (_B_FALSE); abytes = IPV6_ABITS >> 3; tbytes = tlen >> 3; tbits = tlen & 7; for (j = abytes - 1; j >= abytes - tbytes; j--) if (t1.s6_addr[j] != t2.s6_addr[j]) return (_B_FALSE); if (tbits == 0) return (_B_TRUE); /* We only care about the tbits rightmost bits */ mask = 0xff >> (8 - tbits); if ((t1.s6_addr[j] & mask) != (t2.s6_addr[j] & mask)) return (_B_FALSE); return (_B_TRUE); } /* * Lookup prefix structure that matches the prefix and prefix length. * Assumes that the bits after prefixlen might not be zero. */ static struct prefix * prefix_lookup(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct prefix *pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (pr->pr_prefix_len == prefixlen && prefix_equal(prefix, pr->pr_prefix, prefixlen)) return (pr); } return (NULL); } /* * Compare two prefixes that have the same prefix length. * Fails if the prefix length is unreasonable. */ boolean_t prefix_equal(struct in6_addr p1, struct in6_addr p2, int plen) { uchar_t mask; int j, pbytes, pbits; if (plen < 0 || plen > IPV6_ABITS) return (_B_FALSE); pbytes = plen >> 3; pbits = plen & 7; for (j = 0; j < pbytes; j++) if (p1.s6_addr[j] != p2.s6_addr[j]) return (_B_FALSE); if (pbits == 0) return (_B_TRUE); /* Make the N leftmost bits one */ mask = 0xff << (8 - pbits); if ((p1.s6_addr[j] & mask) != (p2.s6_addr[j] & mask)) return (_B_FALSE); return (_B_TRUE); } /* * Set a prefix from an address and a prefix length. * Force all the bits after the prefix length to be zero. */ void prefix_set(struct in6_addr *prefix, struct in6_addr addr, int prefix_len) { uchar_t mask; int j; if (prefix_len < 0 || prefix_len > IPV6_ABITS) return; bzero((char *)prefix, sizeof (*prefix)); for (j = 0; prefix_len > 8; prefix_len -= 8, j++) prefix->s6_addr[j] = addr.s6_addr[j]; /* Make the N leftmost bits one */ mask = 0xff << (8 - prefix_len); prefix->s6_addr[j] = addr.s6_addr[j] & mask; } /* * Lookup a prefix based on the kernel's interface name. */ struct prefix * prefix_lookup_name(struct phyint *pi, char *name) { struct prefix *pr; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup_name(%s, %s)\n", pi->pi_name, name); } if (name[0] == '\0') return (NULL); for (pr = pi->pi_prefix_list; pr != NULL; pr = pr->pr_next) { if (strcmp(name, pr->pr_name) == 0) return (pr); } return (NULL); } /* * Search the phyints list to make sure that this new prefix does * not already exist in any other physical interfaces that have * the same address as this one */ struct prefix * prefix_lookup_addr_match(struct prefix *pr) { char abuf[INET6_ADDRSTRLEN]; struct phyint *pi; struct prefix *otherpr = NULL; struct in6_addr prefix; int prefixlen; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_lookup_addr_match(%s/%u)\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_prefix_len); } prefix = pr->pr_prefix; prefixlen = pr->pr_prefix_len; for (pi = phyints; pi != NULL; pi = pi->pi_next) { otherpr = prefix_lookup(pi, prefix, prefixlen); if (otherpr == pr) continue; if (otherpr != NULL && (otherpr->pr_state & PR_AUTO) && IN6_ARE_ADDR_EQUAL(&pr->pr_address, &otherpr->pr_address)) return (otherpr); } return (NULL); } /* * Initialize a new prefix without setting lifetimes etc. */ struct prefix * prefix_create(struct phyint *pi, struct in6_addr prefix, int prefixlen, uint64_t flags) { struct prefix *pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_create(%s, %s/%u, 0x%llx)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen, flags); } pr = (struct prefix *)calloc(sizeof (struct prefix), 1); if (pr == NULL) { logmsg(LOG_ERR, "prefix_create: out of memory\n"); return (NULL); } /* * The prefix might have non-zero bits after the prefix len bits. * Force them to be zero. */ prefix_set(&pr->pr_prefix, prefix, prefixlen); pr->pr_prefix_len = prefixlen; pr->pr_PreferredLifetime = PREFIX_INFINITY; pr->pr_ValidLifetime = PREFIX_INFINITY; pr->pr_OnLinkLifetime = PREFIX_INFINITY; pr->pr_kernel_state = 0; pr->pr_flags |= flags; prefix_insert(pi, pr); return (pr); } /* * Create a new named prefix. Caller should use prefix_init_from_k * to initialize the content. */ struct prefix * prefix_create_name(struct phyint *pi, char *name) { struct prefix *pr; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_create_name(%s, %s)\n", pi->pi_name, name); } pr = (struct prefix *)calloc(sizeof (struct prefix), 1); if (pr == NULL) { logmsg(LOG_ERR, "prefix_create_name: out of memory\n"); return (NULL); } (void) strncpy(pr->pr_name, name, sizeof (pr->pr_name)); pr->pr_name[sizeof (pr->pr_name) - 1] = '\0'; prefix_insert(pi, pr); return (pr); } /* Insert in linked list */ static void prefix_insert(struct phyint *pi, struct prefix *pr) { pr->pr_next = pi->pi_prefix_list; pr->pr_prev = NULL; if (pi->pi_prefix_list != NULL) pi->pi_prefix_list->pr_prev = pr; pi->pi_prefix_list = pr; pr->pr_physical = pi; } /* * Initialize the prefix from the content of the kernel. * If IFF_ADDRCONF is set we treat it as PR_AUTO (i.e. an addrconf * prefix). However, we cannot derive the lifetime from * the kernel, thus it is set to 1 week. * Ignore the prefix if the interface is not IFF_UP. * If it's from DHCPv6, then we set the netmask. */ int prefix_init_from_k(struct prefix *pr) { struct lifreq lifr; struct sockaddr_in6 *sin6; int sock = pr->pr_physical->pi_sock; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(sock, SIOCGLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get addr)"); goto error; } if (lifr.lifr_addr.ss_family != AF_INET6) { logmsg(LOG_ERR, "ignoring interface %s: not AF_INET6\n", pr->pr_name); goto error; } sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; pr->pr_address = sin6->sin6_addr; if (ioctl(sock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get flags)"); goto error; } pr->pr_flags = lifr.lifr_flags; /* * If this is a DHCPv6 interface, then we control the netmask. */ if (lifr.lifr_flags & IFF_DHCPRUNNING) { struct phyint *pi = pr->pr_physical; struct prefix *pr2; pr->pr_prefix_len = IPV6_ABITS; if (!(lifr.lifr_flags & IFF_UP) || IN6_IS_ADDR_UNSPECIFIED(&pr->pr_address) || IN6_IS_ADDR_LINKLOCAL(&pr->pr_address)) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: " "ignoring DHCP %s not ready\n", pr->pr_name); return (0); } for (pr2 = pi->pi_prefix_list; pr2 != NULL; pr2 = pr2->pr_next) { /* * Examine any non-static (autoconfigured) prefixes as * well as existing DHCP-controlled prefixes for valid * prefix length information. */ if (pr2->pr_prefix_len != IPV6_ABITS && (!(pr2->pr_state & PR_STATIC) || (pr2->pr_flags & IFF_DHCPRUNNING)) && prefix_equal(pr->pr_prefix, pr2->pr_prefix, pr2->pr_prefix_len)) { pr->pr_prefix_len = pr2->pr_prefix_len; break; } } if (pr2 == NULL) { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: no " "saved mask for DHCP %s; need to " "resolicit\n", pr->pr_name); (void) check_to_solicit(pi, RESTART_INIT_SOLICIT); } else { if (debug & D_DHCP) logmsg(LOG_DEBUG, "prefix_init_from_k: using " "%s mask for DHCP %s\n", pr2->pr_name[0] == '\0' ? "saved" : pr2->pr_name, pr->pr_name); prefix_update_dhcp(pr); } /* * If this interface was created using ipadm, store the * addrobj for the DHCPv6 interface in ipmgmtd daemon's * in-memory aobjmap. */ prefix_update_ipadm_addrobj(pr, _B_TRUE); } else { if (ioctl(sock, SIOCGLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_init_from_k: ioctl (get subnet)"); goto error; } if (lifr.lifr_subnet.ss_family != AF_INET6) { logmsg(LOG_ERR, "ignoring interface %s: not AF_INET6\n", pr->pr_name); goto error; } /* * Guard against the prefix having non-zero bits after the * prefix len bits. */ sin6 = (struct sockaddr_in6 *)&lifr.lifr_subnet; pr->pr_prefix_len = lifr.lifr_addrlen; prefix_set(&pr->pr_prefix, sin6->sin6_addr, pr->pr_prefix_len); if (pr->pr_prefix_len != IPV6_ABITS && (pr->pr_flags & IFF_UP) && IN6_ARE_ADDR_EQUAL(&pr->pr_address, &pr->pr_prefix)) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_ERR, "ignoring interface %s: it appears to " "be configured with an invalid interface id " "(%s/%u)\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_prefix_len); goto error; } } pr->pr_kernel_state = 0; if (pr->pr_prefix_len != IPV6_ABITS) pr->pr_kernel_state |= PR_ONLINK; if (!(pr->pr_flags & (IFF_NOLOCAL | IFF_DHCPRUNNING))) pr->pr_kernel_state |= PR_AUTO; if ((pr->pr_flags & IFF_DEPRECATED) && (pr->pr_kernel_state & PR_AUTO)) pr->pr_kernel_state |= PR_DEPRECATED; if (!(pr->pr_flags & IFF_ADDRCONF)) { /* Prevent ndpd from stepping on this prefix */ pr->pr_kernel_state |= PR_STATIC; } pr->pr_state = pr->pr_kernel_state; /* Adjust pr_prefix_len based if PR_AUTO is set */ if (pr->pr_state & PR_AUTO) { pr->pr_prefix_len = IPV6_ABITS - pr->pr_physical->pi_token_length; prefix_set(&pr->pr_prefix, pr->pr_prefix, pr->pr_prefix_len); } /* Can't extract lifetimes from the kernel - use 1 week */ pr->pr_ValidLifetime = NDP_PREFIX_DEFAULT_LIFETIME; pr->pr_PreferredLifetime = NDP_PREFIX_DEFAULT_LIFETIME; pr->pr_OnLinkLifetime = NDP_PREFIX_DEFAULT_LIFETIME; /* * If this is a temp addr, the creation time needs to be set. * Though it won't be entirely accurate, the current time is * an okay approximation. */ if (pr->pr_flags & IFF_TEMPORARY) pr->pr_CreateTime = getcurrenttime() / MILLISEC; if (pr->pr_kernel_state == 0) pr->pr_name[0] = '\0'; return (0); error: /* Pretend that the prefix does not exist in the kernel */ pr->pr_kernel_state = 0; pr->pr_name[0] = '\0'; return (-1); } /* * Delete (unlink and free) and remove from kernel if the prefix * was added by in.ndpd (i.e. PR_STATIC is not set). * Handles delete of things that have not yet been inserted in the list * i.e. pr_physical is NULL. * Removes the ipadm addrobj created for the prefix. */ void prefix_delete(struct prefix *pr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_delete(%s, %s, %s/%u)\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len); } pi = pr->pr_physical; /* Remove non-static prefixes from the kernel. */ pr->pr_state &= PR_STATIC; if (pr->pr_kernel_state != pr->pr_state) prefix_update_k(pr); if (pr->pr_prev == NULL) { if (pi != NULL) pi->pi_prefix_list = pr->pr_next; } else { pr->pr_prev->pr_next = pr->pr_next; } if (pr->pr_next != NULL) pr->pr_next->pr_prev = pr->pr_prev; pr->pr_next = pr->pr_prev = NULL; free(pr); } /* * Toggle one or more IFF_ flags for a prefix. Turn on 'onflags' and * turn off 'offflags'. */ static int prefix_modify_flags(struct prefix *pr, uint64_t onflags, uint64_t offflags) { struct lifreq lifr; struct phyint *pi = pr->pr_physical; uint64_t old_flags; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_modify_flags(%s, %s, %s/%u) " "flags %llx on %llx off %llx\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, pr->pr_flags, onflags, offflags); } /* Assumes that only the PR_STATIC link-local matches the pi_name */ if (!(pr->pr_state & PR_STATIC) && strcmp(pr->pr_name, pi->pi_name) == 0) { logmsg(LOG_ERR, "prefix_modify_flags(%s, on %llx, off %llx): " "name matches interface name\n", pi->pi_name, onflags, offflags); return (-1); } (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (ioctl(pi->pi_sock, SIOCGLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror_pr(pr, "prefix_modify_flags: SIOCGLIFFLAGS"); logmsg(LOG_ERR, "prefix_modify_flags(%s, %s) old 0x%llx" " on 0x%llx off 0x%llx\n", pr->pr_physical->pi_name, pr->pr_name, pr->pr_flags, onflags, offflags); } return (-1); } old_flags = lifr.lifr_flags; lifr.lifr_flags |= onflags; lifr.lifr_flags &= ~offflags; pr->pr_flags = lifr.lifr_flags; if (ioctl(pi->pi_sock, SIOCSLIFFLAGS, (char *)&lifr) < 0) { if (errno != ENXIO) { logperror_pr(pr, "prefix_modify_flags: SIOCSLIFFLAGS"); logmsg(LOG_ERR, "prefix_modify_flags(%s, %s) old 0x%llx" " new 0x%llx on 0x%llx off 0x%llx\n", pr->pr_physical->pi_name, pr->pr_name, old_flags, lifr.lifr_flags, onflags, offflags); } return (-1); } return (0); } /* * Update the subnet mask for this interface under DHCPv6 control. */ void prefix_update_dhcp(struct prefix *pr) { struct lifreq lifr; (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_addr.ss_family = AF_INET6; prefix_set(&((struct sockaddr_in6 *)&lifr.lifr_addr)->sin6_addr, pr->pr_address, pr->pr_prefix_len); lifr.lifr_addrlen = pr->pr_prefix_len; /* * Ignore ENXIO, as the dhcpagent process is responsible for plumbing * and unplumbing these. */ if (ioctl(pr->pr_physical->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) == -1 && errno != ENXIO) logperror_pr(pr, "prefix_update_dhcp: ioctl (set subnet)"); } /* * Make the kernel state match what is in the prefix structure. * This includes creating the prefix (allocating a new interface name) * as well as setting the local address and on-link subnet prefix * and controlling the IFF_ADDRCONF and IFF_DEPRECATED flags. */ void prefix_update_k(struct prefix *pr) { struct lifreq lifr; char abuf[INET6_ADDRSTRLEN]; char buf1[PREFIX_STATESTRLEN], buf2[PREFIX_STATESTRLEN]; struct phyint *pi = pr->pr_physical; struct sockaddr_in6 *sin6; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s, %s, %s/%u) " "from %s to %s\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1)), prefix_print_state(pr->pr_state, buf2, sizeof (buf2))); } if (pr->pr_kernel_state == pr->pr_state) return; /* No changes */ /* Skip static prefixes */ if (pr->pr_state & PR_STATIC) return; if (pr->pr_kernel_state == 0) { uint64_t onflags; /* * Create a new logical interface name and store in pr_name. * Set IFF_ADDRCONF. Do not set an address (yet). */ if (pr->pr_name[0] != '\0') { /* Name already set! */ logmsg(LOG_ERR, "prefix_update_k(%s, %s, %s/%u) " "from %s to %s name is already allocated\n", pr->pr_physical->pi_name, pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1)), prefix_print_state(pr->pr_state, buf2, sizeof (buf2))); return; } (void) strncpy(lifr.lifr_name, pi->pi_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; lifr.lifr_addr.ss_family = AF_UNSPEC; if (ioctl(pi->pi_sock, SIOCLIFADDIF, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCLIFADDIF"); return; } (void) strncpy(pr->pr_name, lifr.lifr_name, sizeof (pr->pr_name)); pr->pr_name[sizeof (pr->pr_name) - 1] = '\0'; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k: new name %s\n", pr->pr_name); } /* * The IFF_TEMPORARY flag might have already been set; if * so, it needs to be or'd into the flags we're turning on. * But be careful, we might be re-creating a manually * removed interface, in which case we don't want to try * to set *all* the flags we might have in our copy of the * flags yet. */ onflags = IFF_ADDRCONF; if (pr->pr_flags & IFF_TEMPORARY) onflags |= IFF_TEMPORARY; if (prefix_modify_flags(pr, onflags, 0) == -1) return; } if ((pr->pr_state & (PR_ONLINK|PR_AUTO)) == 0) { /* Remove the interface */ if (prefix_modify_flags(pr, 0, IFF_UP|IFF_DEPRECATED) == -1) return; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k: remove name %s\n", pr->pr_name); } /* * Assumes that only the PR_STATIC link-local matches * the pi_name */ if (!(pr->pr_state & PR_STATIC) && strcmp(pr->pr_name, pi->pi_name) == 0) { logmsg(LOG_ERR, "prefix_update_k(%s): " "name matches if\n", pi->pi_name); return; } /* Remove logical interface based on pr_name */ lifr.lifr_addr.ss_family = AF_UNSPEC; if (ioctl(pi->pi_sock, SIOCLIFREMOVEIF, (char *)&lifr) < 0 && errno != ENXIO) { logperror_pr(pr, "prefix_update_k: SIOCLIFREMOVEIF"); } pr->pr_kernel_state = 0; pr->pr_name[0] = '\0'; return; } if ((pr->pr_state & PR_AUTO) && !(pr->pr_kernel_state & PR_AUTO)) { /* * Set local address and set the prefix length to 128. * Turn off IFF_NOLOCAL in case it was set. * Turn on IFF_UP. */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_address; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set addr %s " "for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFADDR"); return; } /* * If this interface was created using ipadm, store the * addrobj for the prefix in ipmgmtd daemon's aobjmap. */ prefix_update_ipadm_addrobj(pr, _B_TRUE); if (pr->pr_state & PR_ONLINK) { sin6->sin6_addr = pr->pr_prefix; lifr.lifr_addrlen = pr->pr_prefix_len; } else { sin6->sin6_addr = pr->pr_address; lifr.lifr_addrlen = IPV6_ABITS; } if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%u for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } /* * For ptp interfaces, create a destination based on * prefix and prefix len together with the remote token * extracted from the remote pt-pt address. This is used by * ip to choose a proper source for outgoing packets. */ if (pi->pi_flags & IFF_POINTOPOINT) { int i; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_prefix; for (i = 0; i < 16; i++) { sin6->sin6_addr.s6_addr[i] |= pi->pi_dst_token.s6_addr[i]; } if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) " "set dstaddr %s for PR_AUTO on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFDSTADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFDSTADDR"); return; } } if (prefix_modify_flags(pr, IFF_UP, IFF_NOLOCAL) == -1) return; pr->pr_kernel_state |= PR_AUTO; if (pr->pr_state & PR_ONLINK) pr->pr_kernel_state |= PR_ONLINK; else pr->pr_kernel_state &= ~PR_ONLINK; } if (!(pr->pr_state & PR_AUTO) && (pr->pr_kernel_state & PR_AUTO)) { /* Turn on IFF_NOLOCAL and set the local address to all zero */ if (prefix_modify_flags(pr, IFF_NOLOCAL, 0) == -1) return; (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set addr %s " "for PR_AUTO off\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf))); } if (ioctl(pi->pi_sock, SIOCSLIFADDR, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFADDR"); return; } pr->pr_kernel_state &= ~PR_AUTO; } if ((pr->pr_state & PR_DEPRECATED) && !(pr->pr_kernel_state & PR_DEPRECATED) && (pr->pr_kernel_state & PR_AUTO)) { /* Only applies if PR_AUTO */ if (prefix_modify_flags(pr, IFF_DEPRECATED, 0) == -1) return; pr->pr_kernel_state |= PR_DEPRECATED; } if (!(pr->pr_state & PR_DEPRECATED) && (pr->pr_kernel_state & PR_DEPRECATED)) { if (prefix_modify_flags(pr, 0, IFF_DEPRECATED) == -1) return; pr->pr_kernel_state &= ~PR_DEPRECATED; } if ((pr->pr_state & PR_ONLINK) && !(pr->pr_kernel_state & PR_ONLINK)) { /* Set the subnet and set IFF_UP */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_prefix; lifr.lifr_addrlen = pr->pr_prefix_len; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%d for PR_ONLINK on\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } /* * If we've previously marked the interface "up" while * processing the PR_AUTO flag -- via incoming_prefix_addrconf * -- then there's no need to set it "up" again. We're done; * just set PR_ONLINK to indicate that we've set the subnet. */ if (!(pr->pr_state & PR_AUTO) && prefix_modify_flags(pr, IFF_UP | IFF_NOLOCAL, 0) == -1) return; pr->pr_kernel_state |= PR_ONLINK; } if (!(pr->pr_state & PR_ONLINK) && (pr->pr_kernel_state & PR_ONLINK)) { /* Set the prefixlen to 128 */ (void) strncpy(lifr.lifr_name, pr->pr_name, sizeof (lifr.lifr_name)); lifr.lifr_name[sizeof (lifr.lifr_name) - 1] = '\0'; sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; bzero(sin6, sizeof (struct sockaddr_in6)); sin6->sin6_family = AF_INET6; sin6->sin6_addr = pr->pr_address; lifr.lifr_addrlen = IPV6_ABITS; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "prefix_update_k(%s) set subnet " "%s/%d for PR_ONLINK off\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, abuf, sizeof (abuf)), lifr.lifr_addrlen); } if (ioctl(pi->pi_sock, SIOCSLIFSUBNET, (char *)&lifr) < 0) { logperror_pr(pr, "prefix_update_k: SIOCSLIFSUBNET"); return; } pr->pr_kernel_state &= ~PR_ONLINK; } } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t prefix_timer(struct prefix *pr, uint_t elapsed) { uint_t next = TIMER_INFINITY; char abuf[INET6_ADDRSTRLEN]; if (debug & (D_PREFIX|D_TMP)) { logmsg(LOG_DEBUG, "prefix_timer(%s, %s/%u, %d) " "valid %d pref %d onlink %d\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, elapsed, pr->pr_ValidLifetime, pr->pr_PreferredLifetime, pr->pr_OnLinkLifetime); } /* Exclude static prefixes */ if (pr->pr_state & PR_STATIC) return (next); if (pr->pr_AutonomousFlag && (pr->pr_PreferredLifetime != PREFIX_INFINITY)) { if (pr->pr_PreferredLifetime <= elapsed) { pr->pr_PreferredLifetime = 0; } else { pr->pr_PreferredLifetime -= elapsed; if (pr->pr_PreferredLifetime < next) next = pr->pr_PreferredLifetime; } } if (pr->pr_AutonomousFlag && (pr->pr_ValidLifetime != PREFIX_INFINITY)) { if (pr->pr_ValidLifetime <= elapsed) { pr->pr_ValidLifetime = 0; } else { pr->pr_ValidLifetime -= elapsed; if (pr->pr_ValidLifetime < next) next = pr->pr_ValidLifetime; } } if (pr->pr_OnLinkFlag && (pr->pr_OnLinkLifetime != PREFIX_INFINITY)) { if (pr->pr_OnLinkLifetime <= elapsed) { pr->pr_OnLinkLifetime = 0; } else { pr->pr_OnLinkLifetime -= elapsed; if (pr->pr_OnLinkLifetime < next) next = pr->pr_OnLinkLifetime; } } if (pr->pr_AutonomousFlag && pr->pr_ValidLifetime == 0) pr->pr_state &= ~(PR_AUTO|PR_DEPRECATED); if (pr->pr_AutonomousFlag && pr->pr_PreferredLifetime == 0 && (pr->pr_state & PR_AUTO)) { pr->pr_state |= PR_DEPRECATED; if (debug & D_TMP) logmsg(LOG_WARNING, "prefix_timer: deprecated " "prefix(%s)\n", pr->pr_name); } if (pr->pr_OnLinkFlag && pr->pr_OnLinkLifetime == 0) pr->pr_state &= ~PR_ONLINK; if (pr->pr_state != pr->pr_kernel_state) { /* Might cause prefix to be deleted! */ /* Log a message when an addrconf prefix goes away */ if ((pr->pr_kernel_state & PR_AUTO) && !(pr->pr_state & PR_AUTO)) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_WARNING, "Address removed due to timeout %s\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf))); } prefix_update_k(pr); } return (next); } static char * prefix_print_state(int state, char *buf, int buflen) { char *cp; int cplen = buflen; cp = buf; cp[0] = '\0'; if (state & PR_ONLINK) { if (strlcat(cp, "ONLINK ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_AUTO) { if (strlcat(cp, "AUTO ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_DEPRECATED) { if (strlcat(cp, "DEPRECATED ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } if (state & PR_STATIC) { if (strlcat(cp, "STATIC ", cplen) >= cplen) return (buf); cp += strlen(cp); cplen = buflen - (cp - buf); } return (buf); } static void prefix_print(struct prefix *pr) { char abuf[INET6_ADDRSTRLEN]; char buf1[PREFIX_STATESTRLEN], buf2[PREFIX_STATESTRLEN]; logmsg(LOG_DEBUG, "Prefix name: %s prefix %s/%u state %s " "kernel_state %s\n", pr->pr_name, inet_ntop(AF_INET6, (void *)&pr->pr_prefix, abuf, sizeof (abuf)), pr->pr_prefix_len, prefix_print_state(pr->pr_state, buf2, sizeof (buf2)), prefix_print_state(pr->pr_kernel_state, buf1, sizeof (buf1))); logmsg(LOG_DEBUG, "\tAddress: %s flags %llx in_use %d\n", inet_ntop(AF_INET6, (void *)&pr->pr_address, abuf, sizeof (abuf)), pr->pr_flags, pr->pr_in_use); logmsg(LOG_DEBUG, "\tValidLifetime %u PreferredLifetime %u " "OnLinkLifetime %u\n", pr->pr_ValidLifetime, pr->pr_PreferredLifetime, pr->pr_OnLinkLifetime); logmsg(LOG_DEBUG, "\tOnLink %d Auto %d\n", pr->pr_OnLinkFlag, pr->pr_AutonomousFlag); logmsg(LOG_DEBUG, "\n"); } /* * Lookup advertisement prefix structure that matches the prefix and * prefix length. * Assumes that the bits after prefixlen might not be zero. */ struct adv_prefix * adv_prefix_lookup(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct adv_prefix *adv_pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_lookup(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } for (adv_pr = pi->pi_adv_prefix_list; adv_pr != NULL; adv_pr = adv_pr->adv_pr_next) { if (adv_pr->adv_pr_prefix_len == prefixlen && prefix_equal(prefix, adv_pr->adv_pr_prefix, prefixlen)) return (adv_pr); } return (NULL); } /* * Initialize a new advertisement prefix. */ struct adv_prefix * adv_prefix_create(struct phyint *pi, struct in6_addr prefix, int prefixlen) { struct adv_prefix *adv_pr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_create(%s, %s/%u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&prefix, abuf, sizeof (abuf)), prefixlen); } adv_pr = (struct adv_prefix *)calloc(sizeof (struct adv_prefix), 1); if (adv_pr == NULL) { logmsg(LOG_ERR, "adv_prefix_create: calloc\n"); return (NULL); } /* * The prefix might have non-zero bits after the prefix len bits. * Force them to be zero. */ prefix_set(&adv_pr->adv_pr_prefix, prefix, prefixlen); adv_pr->adv_pr_prefix_len = prefixlen; adv_prefix_insert(pi, adv_pr); return (adv_pr); } /* Insert in linked list */ static void adv_prefix_insert(struct phyint *pi, struct adv_prefix *adv_pr) { adv_pr->adv_pr_next = pi->pi_adv_prefix_list; adv_pr->adv_pr_prev = NULL; if (pi->pi_adv_prefix_list != NULL) pi->pi_adv_prefix_list->adv_pr_prev = adv_pr; pi->pi_adv_prefix_list = adv_pr; adv_pr->adv_pr_physical = pi; } /* * Delete (unlink and free) from our tables. There should be * a corresponding "struct prefix *" which will clean up the kernel * if necessary. adv_prefix is just used for sending out advertisements. */ static void adv_prefix_delete(struct adv_prefix *adv_pr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_delete(%s, %s/%u)\n", adv_pr->adv_pr_physical->pi_name, inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, abuf, sizeof (abuf)), adv_pr->adv_pr_prefix_len); } pi = adv_pr->adv_pr_physical; if (adv_pr->adv_pr_prev == NULL) { if (pi != NULL) pi->pi_adv_prefix_list = adv_pr->adv_pr_next; } else { adv_pr->adv_pr_prev->adv_pr_next = adv_pr->adv_pr_next; } if (adv_pr->adv_pr_next != NULL) adv_pr->adv_pr_next->adv_pr_prev = adv_pr->adv_pr_prev; adv_pr->adv_pr_next = adv_pr->adv_pr_prev = NULL; free(adv_pr); } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t adv_prefix_timer(struct adv_prefix *adv_pr, uint_t elapsed) { int seconds_elapsed = (elapsed + 500) / 1000; /* Rounded */ char abuf[INET6_ADDRSTRLEN]; if (debug & D_PREFIX) { logmsg(LOG_DEBUG, "adv_prefix_timer(%s, %s/%u, %d)\n", adv_pr->adv_pr_physical->pi_name, inet_ntop(AF_INET6, (void *)&adv_pr->adv_pr_prefix, abuf, sizeof (abuf)), adv_pr->adv_pr_prefix_len, elapsed); } /* Decrement Expire time left for real-time lifetimes */ if (adv_pr->adv_pr_AdvValidRealTime) { if (adv_pr->adv_pr_AdvValidExpiration > seconds_elapsed) adv_pr->adv_pr_AdvValidExpiration -= seconds_elapsed; else adv_pr->adv_pr_AdvValidExpiration = 0; } if (adv_pr->adv_pr_AdvPreferredRealTime) { if (adv_pr->adv_pr_AdvPreferredExpiration > seconds_elapsed) { adv_pr->adv_pr_AdvPreferredExpiration -= seconds_elapsed; } else { adv_pr->adv_pr_AdvPreferredExpiration = 0; } } return (TIMER_INFINITY); } static void adv_prefix_print(struct adv_prefix *adv_pr) { print_prefixlist(adv_pr->adv_pr_config); } /* Lookup router on its link-local IPv6 address */ struct router * router_lookup(struct phyint *pi, struct in6_addr addr) { struct router *dr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_lookup(%s, %s)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&addr, abuf, sizeof (abuf))); } for (dr = pi->pi_router_list; dr != NULL; dr = dr->dr_next) { if (bcmp((char *)&addr, (char *)&dr->dr_address, sizeof (addr)) == 0) return (dr); } return (NULL); } /* * Create a default router entry. * The lifetime parameter is in seconds. */ struct router * router_create(struct phyint *pi, struct in6_addr addr, uint_t lifetime) { struct router *dr; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_create(%s, %s, %u)\n", pi->pi_name, inet_ntop(AF_INET6, (void *)&addr, abuf, sizeof (abuf)), lifetime); } dr = (struct router *)calloc(sizeof (struct router), 1); if (dr == NULL) { logmsg(LOG_ERR, "router_create: out of memory\n"); return (NULL); } dr->dr_address = addr; dr->dr_lifetime = lifetime; router_insert(pi, dr); if (dr->dr_lifetime != 0) router_add_k(dr); return (dr); } /* Insert in linked list */ static void router_insert(struct phyint *pi, struct router *dr) { dr->dr_next = pi->pi_router_list; dr->dr_prev = NULL; if (pi->pi_router_list != NULL) pi->pi_router_list->dr_prev = dr; pi->pi_router_list = dr; dr->dr_physical = pi; } /* * Delete (unlink and free). * Handles delete of things that have not yet been inserted in the list * i.e. dr_physical is NULL. */ static void router_delete(struct router *dr) { struct phyint *pi; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_delete(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } pi = dr->dr_physical; if (dr->dr_inkernel && (pi->pi_kernel_state & PI_PRESENT)) router_delete_k(dr); if (dr->dr_prev == NULL) { if (pi != NULL) pi->pi_router_list = dr->dr_next; } else { dr->dr_prev->dr_next = dr->dr_next; } if (dr->dr_next != NULL) dr->dr_next->dr_prev = dr->dr_prev; dr->dr_next = dr->dr_prev = NULL; free(dr); } /* * Update the kernel to match dr_lifetime */ void router_update_k(struct router *dr) { char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_update_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } if (dr->dr_lifetime == 0 && dr->dr_inkernel) { /* Log a message when last router goes away */ if (dr->dr_physical->pi_num_k_routers == 1) { logmsg(LOG_WARNING, "Last default router (%s) removed on %s\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name); } router_delete(dr); } else if (dr->dr_lifetime != 0 && !dr->dr_inkernel) router_add_k(dr); } /* * Called with the number of millseconds elapsed since the last call. * Determines if any timeout event has occurred and * returns the number of milliseconds until the next timeout event. * Returns TIMER_INFINITY for "never". */ uint_t router_timer(struct router *dr, uint_t elapsed) { uint_t next = TIMER_INFINITY; char abuf[INET6_ADDRSTRLEN]; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_timer(%s, %s, %u, %d)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime, elapsed); } if (dr->dr_lifetime <= elapsed) { dr->dr_lifetime = 0; } else { dr->dr_lifetime -= elapsed; if (dr->dr_lifetime < next) next = dr->dr_lifetime; } if (dr->dr_lifetime == 0) { /* Log a message when last router goes away */ if (dr->dr_physical->pi_num_k_routers == 1) { logmsg(LOG_WARNING, "Last default router (%s) timed out on %s\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name); } router_delete(dr); } return (next); } /* * Add a default route to the kernel (unless the lifetime is zero) * Handles onlink default routes. */ static void router_add_k(struct router *dr) { struct phyint *pi = dr->dr_physical; char abuf[INET6_ADDRSTRLEN]; int rlen; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_add_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } rta_gateway->sin6_addr = dr->dr_address; rta_ifp->sdl_index = if_nametoindex(pi->pi_name); if (rta_ifp->sdl_index == 0) { logperror_pi(pi, "router_add_k: if_nametoindex"); return; } rt_msg->rtm_flags = RTF_GATEWAY; rt_msg->rtm_type = RTM_ADD; rt_msg->rtm_seq = ++rtmseq; rlen = write(rtsock, rt_msg, rt_msg->rtm_msglen); if (rlen < 0) { if (errno != EEXIST) { logperror_pi(pi, "router_add_k: RTM_ADD"); return; } } else if (rlen < rt_msg->rtm_msglen) { logmsg(LOG_ERR, "router_add_k: write to routing socket got " "only %d for rlen (interface %s)\n", rlen, pi->pi_name); return; } dr->dr_inkernel = _B_TRUE; pi->pi_num_k_routers++; } /* * Delete a route from the kernel. * Handles onlink default routes. */ static void router_delete_k(struct router *dr) { struct phyint *pi = dr->dr_physical; char abuf[INET6_ADDRSTRLEN]; int rlen; if (debug & D_ROUTER) { logmsg(LOG_DEBUG, "router_delete_k(%s, %s, %u)\n", dr->dr_physical->pi_name, inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_lifetime); } rta_gateway->sin6_addr = dr->dr_address; rta_ifp->sdl_index = if_nametoindex(pi->pi_name); if (rta_ifp->sdl_index == 0) { logperror_pi(pi, "router_delete_k: if_nametoindex"); return; } rt_msg->rtm_flags = RTF_GATEWAY; rt_msg->rtm_type = RTM_DELETE; rt_msg->rtm_seq = ++rtmseq; rlen = write(rtsock, rt_msg, rt_msg->rtm_msglen); if (rlen < 0) { if (errno != ESRCH) { logperror_pi(pi, "router_delete_k: RTM_DELETE"); } } else if (rlen < rt_msg->rtm_msglen) { logmsg(LOG_ERR, "router_delete_k: write to routing socket got " "only %d for rlen (interface %s)\n", rlen, pi->pi_name); } dr->dr_inkernel = _B_FALSE; pi->pi_num_k_routers--; } static void router_print(struct router *dr) { char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "Router %s on %s inkernel %d lifetime %u\n", inet_ntop(AF_INET6, (void *)&dr->dr_address, abuf, sizeof (abuf)), dr->dr_physical->pi_name, dr->dr_inkernel, dr->dr_lifetime); } void phyint_print_all(void) { struct phyint *pi; for (pi = phyints; pi != NULL; pi = pi->pi_next) { phyint_print(pi); } } void phyint_cleanup(struct phyint *pi) { pi->pi_state = 0; pi->pi_kernel_state = 0; if (pi->pi_AdvSendAdvertisements) { check_to_advertise(pi, ADV_OFF); } else { check_to_solicit(pi, SOLICIT_OFF); } while (pi->pi_router_list) router_delete(pi->pi_router_list); (void) poll_remove(pi->pi_sock); (void) close(pi->pi_sock); pi->pi_sock = -1; pi->pi_stateless = pi->pi_StatelessAddrConf; pi->pi_stateful = pi->pi_StatefulAddrConf; pi->pi_ipadm_aobjname[0] = '\0'; pi->pi_ifaddr = in6addr_any; } /* * Sets/removes the ipadm address object name for the given prefix. */ void prefix_update_ipadm_addrobj(struct prefix *pr, boolean_t add) { struct phyint *pi = pr->pr_physical; int lnum = 0; char *cp; ipadm_handle_t iph; ipadm_status_t status; /* * If ipadm was used to autoconfigure this interface, * pi_ipadm_aobjname will contain the address object name * that is used to identify the addresses. Use the same * address object name for this prefix. */ if (pi->pi_ipadm_aobjname[0] == '\0' || pr->pr_name[0] == '\0' || IN6_IS_ADDR_LINKLOCAL(&pr->pr_address) || (!(pr->pr_flags & IFF_ADDRCONF) && !(pr->pr_flags & IFF_DHCPRUNNING))) { return; } if ((status = ipadm_open(&iph, 0)) != IPADM_SUCCESS) { logmsg(LOG_ERR, "Could not open handle to libipadm: %s\n", ipadm_status2str(status)); return; } cp = strrchr(pr->pr_name, ':'); if (cp != NULL) lnum = atoi(++cp); if (add) { status = ipadm_add_aobjname(iph, pi->pi_name, AF_INET6, pi->pi_ipadm_aobjname, IPADM_ADDR_IPV6_ADDRCONF, lnum); } else { status = ipadm_delete_aobjname(iph, pi->pi_name, AF_INET6, pi->pi_ipadm_aobjname, IPADM_ADDR_IPV6_ADDRCONF, lnum); } /* Ignore the error if the ipmgmtd daemon is not running */ if (status != IPADM_SUCCESS && status != IPADM_IPC_ERROR) { logmsg(LOG_ERR, "ipadm error in %s '%s' : %s\n", (add ? "adding" : "deleting"), pi->pi_ipadm_aobjname, ipadm_status2str(status)); } ipadm_close(iph); } /* * 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _NDPD_TABLES_H #define _NDPD_TABLES_H #ifdef __cplusplus extern "C" { #endif #include #include enum adv_states { NO_ADV = 0, REG_ADV, INIT_ADV, SOLICIT_ADV, FINAL_ADV }; enum adv_events { ADV_OFF, START_INIT_ADV, START_FINAL_ADV, RECEIVED_SOLICIT, ADV_TIMER }; enum solicit_states { NO_SOLICIT = 0, INIT_SOLICIT, DONE_SOLICIT }; enum solicit_events { SOLICIT_OFF, START_INIT_SOLICIT, SOL_TIMER, SOLICIT_DONE, RESTART_INIT_SOLICIT }; /* * A doubly linked list of all physical interfaces that each contain a * doubly linked list of prefixes (i.e. logical interfaces) and default * routers. */ struct phyint { struct phyint *pi_next; struct phyint *pi_prev; struct prefix *pi_prefix_list; /* Doubly linked prefixes */ struct router *pi_router_list; /* Doubly linked routers */ struct adv_prefix *pi_adv_prefix_list; /* Doubly linked adv.prefixes */ uint_t pi_index; /* Identifier > 0 */ char pi_name[LIFNAMSIZ]; /* Used to identify it */ int pi_sock; /* For sending and receiving */ struct in6_addr pi_ifaddr; /* Local address */ uint64_t pi_flags; /* IFF_* flags */ uint_t pi_mtu; /* From SIOCGLIFMTU */ struct in6_addr pi_token; uint_t pi_token_length; boolean_t pi_stateless; boolean_t pi_stateful; struct in6_addr pi_tmp_token; /* For RFC3041 addrs */ struct in6_addr pi_dst_token; /* For POINTOPOINT */ uint_t pi_state; /* PI_* below */ uint_t pi_kernel_state; /* PI_* below */ uint_t pi_num_k_routers; /* # routers in kernel */ uint_t pi_reach_time_since_random; /* In milliseconds */ /* Applies if pi_AdvSendAdvertisements */ uint_t pi_adv_time_left; /* In milliseconds */ uint_t pi_adv_time_since_sent; /* In milliseconds */ enum adv_states pi_adv_state; uint_t pi_adv_count; /* Applies if not pi_AdvSendAdvertisements */ uint_t pi_sol_time_left; /* In milliseconds */ enum solicit_states pi_sol_state; uint_t pi_sol_count; /* Interface specific configurable variables */ struct confvar pi_config[I_IFSIZE]; #define pi_DupAddrDetectTransmits pi_config[I_DupAddrDetectTransmits].cf_value #define pi_AdvSendAdvertisements pi_config[I_AdvSendAdvertisements].cf_value #define pi_MaxRtrAdvInterval pi_config[I_MaxRtrAdvInterval].cf_value #define pi_MinRtrAdvInterval pi_config[I_MinRtrAdvInterval].cf_value #define pi_AdvManagedFlag pi_config[I_AdvManagedFlag].cf_value #define pi_AdvOtherConfigFlag pi_config[I_AdvOtherConfigFlag].cf_value #define pi_AdvLinkMTU pi_config[I_AdvLinkMTU].cf_value #define pi_AdvReachableTime pi_config[I_AdvReachableTime].cf_value #define pi_AdvRetransTimer pi_config[I_AdvRetransTimer].cf_value #define pi_AdvCurHopLimit pi_config[I_AdvCurHopLimit].cf_value #define pi_AdvDefaultLifetime pi_config[I_AdvDefaultLifetime].cf_value #define pi_StatelessAddrConf pi_config[I_StatelessAddrConf].cf_value #define pi_TmpAddrsEnabled pi_config[I_TmpAddrsEnabled].cf_value #define pi_TmpValidLifetime pi_config[I_TmpValidLifetime].cf_value #define pi_TmpPreferredLifetime pi_config[I_TmpPreferredLifetime].cf_value #define pi_TmpRegenAdvance pi_config[I_TmpRegenAdvance].cf_value #define pi_TmpMaxDesyncFactor pi_config[I_TmpMaxDesyncFactor].cf_value #define pi_StatefulAddrConf pi_config[I_StatefulAddrConf].cf_value /* Recorded variables for RFC3041 addresses */ uint_t pi_TmpDesyncFactor; /* In milliseconds */ uint_t pi_TmpRegenCountdown; /* In milliseconds */ /* Recorded variables on node/host */ uint_t pi_LinkMTU; uint_t pi_CurHopLimit; uint_t pi_BaseReachableTime; /* In milliseconds */ uint_t pi_ReachableTime; /* In milliseconds */ /* * The above value should be a uniformly-distributed random * value between ND_MIN_RANDOM_FACTOR and * ND_MAX_RANDOM_FACTOR times BaseReachableTime * milliseconds. A new random value should be * calculated when BaseReachableTime changes (due to * Router Advertisements) or at least every few hours * even if no Router Advertisements are received. * Tracked using pi_each_time_since_random. */ uint_t pi_RetransTimer; /* In milliseconds */ uint_t pi_ra_flags; /* Detect when to start DHCP */ boolean_t pi_autoconf; /* Enable/Disable autoconfiguration */ boolean_t pi_default_token; /* Use default token */ char pi_ipadm_aobjname[IPADM_AOBJSIZ]; }; /* * pi_state/pr_kernel_state values */ #define PI_PRESENT 0x01 #define PI_JOINED_ALLNODES 0x02 /* allnodes multicast joined */ #define PI_JOINED_ALLROUTERS 0x04 /* allrouters multicast joined */ /* * Prefix configuration variable indices */ #define I_AdvValidLifetime 0 /* In seconds */ #define I_AdvOnLinkFlag 1 #define I_AdvPreferredLifetime 2 /* In seconds */ #define I_AdvAutonomousFlag 3 #define I_AdvValidExpiration 4 /* Seconds left */ #define I_AdvPreferredExpiration 5 /* Seconds left */ #define I_PREFIXSIZE 6 /* # of variables */ /* * A doubly-linked list of prefixes for onlink and addrconf. * ("Prefixes" in this context are identical to logical interfaces.) */ struct prefix { struct prefix *pr_next; /* Next prefix for this physical */ struct prefix *pr_prev; /* Prev prefix for this physical */ struct phyint *pr_physical; /* Back pointer */ struct in6_addr pr_prefix; /* Used to indentify prefix */ uint_t pr_prefix_len; /* Num bits valid */ char pr_name[LIFNAMSIZ]; struct in6_addr pr_address; uint64_t pr_flags; /* IFF_* flags */ uint_t pr_state; /* PR_ONLINK | PR_AUTO etc */ uint_t pr_kernel_state; /* PR_ONLINK | PR_AUTO etc */ boolean_t pr_in_use; /* To detect removed prefixes */ /* Recorded variables on node/host */ uint_t pr_ValidLifetime; /* In ms w/ 2 hour rule */ uint_t pr_PreferredLifetime; /* In millseconds */ uint_t pr_OnLinkLifetime; /* ms valid w/o 2 hour rule */ boolean_t pr_OnLinkFlag; boolean_t pr_AutonomousFlag; uint_t pr_CreateTime; /* tmpaddr creation time */ /* in SECONDS */ uint_t pr_attempts; /* attempts to configure */ }; /* * Flags used for pr_kernel_state and pr_state where the latter is * user-level state. */ #define PR_ONLINK 0x01 /* On-link */ #define PR_AUTO 0x02 /* Stateless addrconf */ #define PR_DEPRECATED 0x04 /* Address is deprecated */ #define PR_STATIC 0x08 /* Not created by ndpd */ /* * The sum of all possible state string lengths, plus terminating * null character; if new states are added, this needs to be updated. * Useful for passing an appropriately sized buffer to prefix_print_state(). * * Current strings: "ONLINK ", "AUTO ", "DEPRECATED ", "STATIC ", "\n" * 7 + 5 + 11 + 7 + 1 */ #define PREFIX_STATESTRLEN 31 /* Prefix used for storing advertisement specific stuff */ struct adv_prefix { struct adv_prefix *adv_pr_next; /* Next prefix */ struct adv_prefix *adv_pr_prev; /* Prev prefix */ struct phyint *adv_pr_physical; /* Back pointer */ struct in6_addr adv_pr_prefix; /* Used to indentify prefix */ uint_t adv_pr_prefix_len; /* Num bits valid */ /* Used when sending advertisements */ struct confvar adv_pr_config[I_PREFIXSIZE]; #define adv_pr_AdvValidLifetime adv_pr_config[I_AdvValidLifetime].cf_value #define adv_pr_AdvOnLinkFlag adv_pr_config[I_AdvOnLinkFlag].cf_value #define adv_pr_AdvPreferredLifetime \ adv_pr_config[I_AdvPreferredLifetime].cf_value #define adv_pr_AdvAutonomousFlag \ adv_pr_config[I_AdvAutonomousFlag].cf_value #define adv_pr_AdvValidExpiration \ adv_pr_config[I_AdvValidExpiration].cf_value #define adv_pr_AdvPreferredExpiration \ adv_pr_config[I_AdvPreferredExpiration].cf_value /* The two below are set if the timers decrement in real time */ #define adv_pr_AdvValidRealTime \ adv_pr_config[I_AdvValidExpiration].cf_notdefault #define adv_pr_AdvPreferredRealTime \ adv_pr_config[I_AdvPreferredExpiration].cf_notdefault }; /* * Doubly-linked list of default routers on a phyint. */ struct router { struct router *dr_next; /* Next router for this physical */ struct router *dr_prev; /* Prev router for this physical */ struct phyint *dr_physical; /* Back pointer */ struct in6_addr dr_address; /* Used to identify the router */ uint_t dr_lifetime; /* In milliseconds */ boolean_t dr_inkernel; /* Route added to kernel */ }; /* * Globals */ extern struct phyint *phyints; extern int num_of_phyints; /* * Functions */ extern uint_t getcurrenttime(void); extern struct phyint *phyint_lookup(char *name); extern struct phyint *phyint_lookup_on_index(uint_t ifindex); extern struct phyint *phyint_create(char *name); extern int phyint_init_from_k(struct phyint *pi); extern void phyint_delete(struct phyint *pi); extern uint_t phyint_timer(struct phyint *pi, uint_t elapsed); extern void phyint_print_all(void); extern int phyint_get_lla(struct phyint *pi, struct lifreq *lifrp); extern void phyint_reach_random(struct phyint *pi, boolean_t set_needed); extern void phyint_cleanup(struct phyint *pi); extern boolean_t tmptoken_create(struct phyint *pi); extern void tmptoken_delete(struct phyint *pi); extern uint_t tmptoken_timer(struct phyint *pi, uint_t elapsed); extern boolean_t token_equal(struct in6_addr t1, struct in6_addr t2, int bits); extern struct prefix *prefix_create(struct phyint *pi, struct in6_addr addr, int addrlen, uint64_t flags); extern struct prefix *prefix_lookup_name(struct phyint *pi, char *name); extern struct prefix *prefix_lookup_addr_match(struct prefix *pr); extern struct prefix *prefix_create_name(struct phyint *pi, char *name); extern int prefix_init_from_k(struct prefix *pr); extern void prefix_delete(struct prefix *pr); extern boolean_t prefix_equal(struct in6_addr p1, struct in6_addr p2, int bits); extern void prefix_update_dhcp(struct prefix *pr); extern void prefix_update_k(struct prefix *pr); extern uint_t prefix_timer(struct prefix *pr, uint_t elapsed); extern uint_t adv_prefix_timer(struct adv_prefix *adv_pr, uint_t elapsed); extern struct prefix *prefix_lookup_addr(struct phyint *pi, struct in6_addr prefix); extern struct adv_prefix *adv_prefix_lookup(struct phyint *pi, struct in6_addr addr, int addrlen); extern struct adv_prefix *adv_prefix_create(struct phyint *pi, struct in6_addr addr, int addrlen); extern struct router *router_lookup(struct phyint *pi, struct in6_addr addr); extern struct router *router_create(struct phyint *pi, struct in6_addr addr, uint_t lifetime); extern void router_update_k(struct router *dr); extern uint_t router_timer(struct router *dr, uint_t elapsed); extern void check_to_advertise(struct phyint *pi, enum adv_events event); extern void check_to_solicit(struct phyint *pi, enum solicit_events event); extern uint_t advertise_event(struct phyint *pi, enum adv_events event, uint_t elapsed); extern uint_t solicit_event(struct phyint *pi, enum solicit_events event, uint_t elapsed); extern void print_route_sol(char *str, struct phyint *pi, struct nd_router_solicit *rs, int len, struct sockaddr_in6 *addr); extern void print_route_adv(char *str, struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *addr); extern void print_iflist(struct confvar *confvar); extern void print_prefixlist(struct confvar *confvar); extern void in_data(struct phyint *pi); extern void start_dhcp(struct phyint *pi); extern void release_dhcp(struct phyint *pi); extern void incoming_ra(struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *from, boolean_t loopback); extern boolean_t incoming_prefix_addrconf_process(struct phyint *pi, struct prefix *pr, uchar_t *opt, struct sockaddr_in6 *from, boolean_t loopback, boolean_t new_prefix); extern void incoming_prefix_onlink_process(struct prefix *pr, uchar_t *opt); extern void check_autoconf_var_consistency(struct phyint *, boolean_t, boolean_t); extern void prefix_update_ipadm_addrobj(struct prefix *pr, boolean_t add); #ifdef __cplusplus } #endif #endif /* _NDPD_TABLES_H */ /* * 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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include "defs.h" #include "tables.h" static void print_opt(struct nd_opt_hdr *opt, int len); void print_route_sol(char *str, struct phyint *pi, struct nd_router_solicit *rs, int len, struct sockaddr_in6 *addr) { struct nd_opt_hdr *opt; char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "%s %s (%d bytes) on %s\n", str, inet_ntop(addr->sin6_family, (void *)&addr->sin6_addr, abuf, sizeof (abuf)), len, pi->pi_name); len -= sizeof (*rs); opt = (struct nd_opt_hdr *)&rs[1]; print_opt(opt, len); } void print_route_adv(char *str, struct phyint *pi, struct nd_router_advert *ra, int len, struct sockaddr_in6 *addr) { struct nd_opt_hdr *opt; char abuf[INET6_ADDRSTRLEN]; logmsg(LOG_DEBUG, "%s %s (%d bytes) on %s\n", str, inet_ntop(addr->sin6_family, (void *)&addr->sin6_addr, abuf, sizeof (abuf)), len, pi->pi_name); logmsg(LOG_DEBUG, "\tMax hop limit: %u\n", ra->nd_ra_curhoplimit); logmsg(LOG_DEBUG, "\tManaged address configuration: %s\n", (ra->nd_ra_flags_reserved & ND_RA_FLAG_MANAGED) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\tOther configuration flag: %s\n", (ra->nd_ra_flags_reserved & ND_RA_FLAG_OTHER) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\tRouter lifetime: %u\n", ntohs(ra->nd_ra_router_lifetime)); logmsg(LOG_DEBUG, "\tReachable timer: %u\n", ntohl(ra->nd_ra_reachable)); logmsg(LOG_DEBUG, "\tReachable retrans timer: %u\n", ntohl(ra->nd_ra_retransmit)); len -= sizeof (*ra); opt = (struct nd_opt_hdr *)&ra[1]; print_opt(opt, len); } static void print_opt(struct nd_opt_hdr *opt, int len) { struct nd_opt_prefix_info *po; struct nd_opt_mtu *mo; struct nd_opt_lla *lo; int optlen; char abuf[INET6_ADDRSTRLEN]; char llabuf[BUFSIZ]; while (len >= sizeof (struct nd_opt_hdr)) { optlen = opt->nd_opt_len * 8; if (optlen == 0) { logmsg(LOG_DEBUG, "Zero length option!\n"); break; } switch (opt->nd_opt_type) { case ND_OPT_PREFIX_INFORMATION: po = (struct nd_opt_prefix_info *)opt; if (optlen != sizeof (*po) || optlen > len) break; logmsg(LOG_DEBUG, "\tPrefix: %s/%u\n", inet_ntop(AF_INET6, (void *)&po->nd_opt_pi_prefix, abuf, sizeof (abuf)), po->nd_opt_pi_prefix_len); logmsg(LOG_DEBUG, "\t\tOn link flag:%s\n", (po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_ONLINK) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\t\tAuto addrconf flag:%s\n", (po->nd_opt_pi_flags_reserved & ND_OPT_PI_FLAG_AUTO) ? "Set" : "Not set"); logmsg(LOG_DEBUG, "\t\tValid time: %u\n", ntohl(po->nd_opt_pi_valid_time)); logmsg(LOG_DEBUG, "\t\tPreferred time: %u\n", ntohl(po->nd_opt_pi_preferred_time)); break; case ND_OPT_MTU: mo = (struct nd_opt_mtu *)opt; if (optlen != sizeof (*mo) || optlen > len) break; logmsg(LOG_DEBUG, "\tMTU: %d\n", ntohl(mo->nd_opt_mtu_mtu)); break; case ND_OPT_SOURCE_LINKADDR: lo = (struct nd_opt_lla *)opt; if (optlen < 8 || optlen > len) break; (void) fmt_lla(llabuf, sizeof (llabuf), lo->nd_opt_lla_hdw_addr, optlen - sizeof (nd_opt_hdr_t)); logmsg(LOG_DEBUG, "\tSource LLA: len %d <%s>\n", optlen - sizeof (nd_opt_hdr_t), llabuf); break; case ND_OPT_TARGET_LINKADDR: lo = (struct nd_opt_lla *)opt; if (optlen < 8|| optlen > len) break; (void) fmt_lla(llabuf, sizeof (llabuf), lo->nd_opt_lla_hdw_addr, optlen - sizeof (nd_opt_hdr_t)); logmsg(LOG_DEBUG, "\tTarget LLA: len %d <%s>\n", optlen - sizeof (nd_opt_hdr_t), llabuf); break; case ND_OPT_REDIRECTED_HEADER: logmsg(LOG_DEBUG, "\tRedirected header option!\n"); break; default: logmsg(LOG_DEBUG, "Unknown option %d (0x%x)\n", opt->nd_opt_type, opt->nd_opt_type); break; } opt = (struct nd_opt_hdr *)((char *)opt + optlen); len -= optlen; } } char * fmt_lla(char *llabuf, int bufsize, uchar_t *lla, int llalen) { int i; char *cp = llabuf; for (i = 0; i < llalen; i++) { if (i == llalen - 1) /* Last byte? */ (void) snprintf(cp, bufsize, "%02x", lla[i] & 0xFF); else (void) snprintf(cp, bufsize, "%02x:", lla[i] & 0xFF); bufsize -= strlen(cp); cp += strlen(cp); } return (llabuf); } # # 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. # # Copyright (c) 2018, Joyent, Inc. SVCMETHOD= svc-ripng MANIFEST= ripng.xml PROG= in.ripngd OBJS= if.o input.o main.o output.o startup.o tables.o timer.o trace.o SRCS= $(OBJS:%.o=%.c) include ../../../Makefile.cmd ROOTMANIFESTDIR= $(ROOTSVCNETWORKROUTING) # these #defines are required to use UNIX 98 interfaces _D_UNIX98_EXTN= -D_XOPEN_SOURCE=500 -D__EXTENSIONS__ $(OBJS) : CFLAGS += $(CCVERBOSE) $(OBJS) : CPPFLAGS += $(_D_UNIX98_EXTN) LINTFLAGS += $(_D_UNIX98_EXTN) # not linted SMATCH=off # in.ripngd uses the ancillary data feature which is available only through # UNIX 98 standards version of Socket interface. This interface is supposed to # be accessed by -lxnet. In addition, -lsocket is used to # capture new not-yet-standard interfaces. Someday -lxnet alone should be enough # when IPv6 inspired new interfaces are part of standards. LDLIBS += -lxnet -lsocket .KEEP_STATE: .PARALLEL: $(OBJS) all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD) check: $(CHKMANIFEST) 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, 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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #ifndef _IN_RIPNGD_DEFS_H #define _IN_RIPNGD_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 "table.h" #include "trace.h" #include "interface.h" #define PATH_PID "/var/run/in.ripngd.pid" /* * Timer values (in seconds) used in managing the routing table. * Every update forces an entry's timer to be reset. After * EXPIRE_TIME without updates, the entry is marked invalid, * but held onto until GARBAGE_TIME so that others may * see it "be deleted". */ #define EXPIRE_TIME 180 /* time to mark entry invalid */ #define GARBAGE_TIME 300 /* time to garbage collect */ #define MIN_SUPPLY_TIME 15 /* min. time to supply tables */ #define MAX_SUPPLY_TIME 45 /* max. time to supply tables */ #define MIN_WAIT_TIME 1 /* min. interval to multicast changes */ #define MAX_WAIT_TIME 5 /* max. time to delay changes */ /* * Return a random number from a an range inclusive of the endpoints */ #define GET_RANDOM(LOW, HIGH) (random() % ((HIGH) - (LOW) + 1) + (LOW)) /* * When we find any interfaces marked down we rescan the * kernel every CHECK_INTERVAL seconds to see if they've * come up. */ #define CHECK_INTERVAL 60 #define START_POLL_SIZE 5 #define min(a, b) ((a) > (b) ? (b) : (a)) /* * The maximum receive buffer size is controlled via Solaris' NDD udp_max_buf * tunable. */ #define RCVBUFSIZ 65536 #define TIME_TO_MSECS(tval) ((tval).tv_sec * 1000 + (tval).tv_usec / 1000) #define HOPCNT_INFINITY 16 /* RFC 2080, section 2.1 */ #define HOPCNT_NEXTHOP 255 /* RFC 2080, section 2.1.1 */ /* * XXX Some of these are defined in under _KERNEL (but should be * defined in for completeness). */ #define IPV6_MAX_HOPS 255 /* Max IPv6 hops */ #define IPV6_MAX_PACKET 65535 /* maximum IPv6 packet size */ #define IPV6_MIN_MTU 1280 /* Minimum IPv6 MTU */ extern struct sockaddr_in6 allrouters; extern struct in6_addr allrouters_in6; extern char *control; extern boolean_t dopoison; extern struct interface *ifnet; extern boolean_t install; extern int iocsoc; extern struct timeval lastfullupdate; extern struct timeval lastmcast; extern int max_poll_ifs; extern struct rip6 *msg; extern boolean_t needupdate; extern struct timeval nextmcast; extern struct timeval now; extern char *packet; extern struct pollfd *poll_ifs; extern int poll_ifs_num; extern int rip6_port; extern int supplyinterval; extern boolean_t supplier; extern void dynamic_update(struct interface *); extern void in_data(struct interface *); extern void initifs(void); extern void sendpacket(struct sockaddr_in6 *, struct interface *, int, int); extern void setup_rtsock(void); extern void solicitall(struct sockaddr_in6 *); extern void supply(struct sockaddr_in6 *, struct interface *, int, boolean_t); extern void supplyall(struct sockaddr_in6 *, int, struct interface *, boolean_t); extern void term(void); extern void timer(void); extern void timevaladd(struct timeval *, struct timeval *); #ifdef __cplusplus } #endif #endif /* _IN_RIPNGD_DEFS_H */ /* * 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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Routing Table Management Daemon */ #include "defs.h" /* * Find the interface with given name. */ struct interface * if_ifwithname(char *name) { struct interface *ifp; for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) { if (ifp->int_name != NULL && strcmp(ifp->int_name, name) == 0) break; } return (ifp); } /* * An interface has declared itself down - remove it completely * from our routing tables but keep the interface structure around. */ void if_purge(struct interface *pifp) { rtpurgeif(pifp); pifp->int_flags &= ~RIP6_IFF_UP; } static void if_dump2(FILE *fp) { struct interface *ifp; char buf1[INET6_ADDRSTRLEN]; static struct bits { uint_t t_bits; char *t_name; } flagbits[] = { /* BEGIN CSTYLED */ { RIP6_IFF_UP, "UP" }, { RIP6_IFF_POINTOPOINT, "POINTOPOINT" }, { RIP6_IFF_MARKED, "MARKED" }, { RIP6_IFF_NORTEXCH, "NORTEXCH" }, { RIP6_IFF_PRIVATE, "PRIVATE" }, { 0, NULL } /* END CSTYLED */ }; struct bits *p; char c; boolean_t first; for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) { (void) fprintf(fp, "interface %s:\n", (ifp->int_name != NULL) ? ifp->int_name : "(noname)"); (void) fprintf(fp, "\tflags "); c = ' '; for (first = _B_TRUE, p = flagbits; p->t_bits > 0; p++) { if ((ifp->int_flags & p->t_bits) == 0) continue; (void) fprintf(fp, "%c%s", c, p->t_name); if (first) { c = '|'; first = _B_FALSE; } } if (first) (void) fprintf(fp, " 0"); (void) fprintf(fp, "\n\tpackets received %d\n", ifp->int_ipackets); (void) fprintf(fp, "\tpackets sent %d\n", ifp->int_opackets); (void) fprintf(fp, "\ttransitions %d\n", ifp->int_transitions); if ((ifp->int_flags & RIP6_IFF_UP) == 0) continue; if (ifp->int_flags & RIP6_IFF_POINTOPOINT) { (void) fprintf(fp, "\tlocal %s\n", inet_ntop(AF_INET6, (void *)&ifp->int_addr, buf1, sizeof (buf1))); (void) fprintf(fp, "\tremote %s\n", inet_ntop(AF_INET6, (void *)&ifp->int_dstaddr, buf1, sizeof (buf1))); } else { (void) fprintf(fp, "\tprefix %s/%d\n", inet_ntop(AF_INET6, (void *)&ifp->int_addr, buf1, sizeof (buf1)), ifp->int_prefix_length); } (void) fprintf(fp, "\tmetric %d\n", ifp->int_metric); (void) fprintf(fp, "\tmtu %d\n", ifp->int_mtu); } (void) fflush(fp); } void if_dump(void) { if (ftrace != NULL) if_dump2(ftrace); else if_dump2(stderr); } /* * 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 1999 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Routing Table Management Daemon */ #include "defs.h" static char buf1[INET6_ADDRSTRLEN]; static char buf2[INET6_ADDRSTRLEN]; static void rip_input(struct sockaddr_in6 *from, int size, uint_t hopcount, struct interface *ifp); /* * Return a pointer to the specified option buffer. * If not found return NULL. */ static void * find_ancillary(struct msghdr *rmsg, int cmsg_type) { struct cmsghdr *cmsg; for (cmsg = CMSG_FIRSTHDR(rmsg); cmsg != NULL; cmsg = CMSG_NXTHDR(rmsg, cmsg)) { if (cmsg->cmsg_level == IPPROTO_IPV6 && cmsg->cmsg_type == cmsg_type) { return (CMSG_DATA(cmsg)); } } return (NULL); } /* * Read a packet and passes it to rip_input() for processing. */ void in_data(struct interface *ifp) { struct sockaddr_in6 from; int len; struct msghdr rmsg; struct iovec iov; uchar_t *hopcntopt; iov.iov_base = packet; iov.iov_len = IPV6_MAX_PACKET; rmsg.msg_name = &from; rmsg.msg_namelen = (socklen_t)sizeof (from); rmsg.msg_iov = &iov; rmsg.msg_iovlen = 1; rmsg.msg_control = control; rmsg.msg_controllen = IPV6_MAX_PACKET; if ((len = recvmsg(ifp->int_sock, &rmsg, 0)) < 0) { /* * Only syslog if a true error occurred. */ if (errno != EINTR) syslog(LOG_ERR, "in_data: recvmsg: %m"); return; } if (len == 0) return; if (tracing & INPUT_BIT) { (void) inet_ntop(from.sin6_family, &from.sin6_addr, buf1, sizeof (buf1)); } /* Ignore packets > 64k or control buffers that don't fit */ if (rmsg.msg_flags & (MSG_TRUNC | MSG_CTRUNC)) { if (tracing & INPUT_BIT) { (void) fprintf(stderr, "Truncated message: msg_flags 0x%x from %s\n", rmsg.msg_flags, buf1); } return; } if ((hopcntopt = find_ancillary(&rmsg, IPV6_HOPLIMIT)) == NULL) { if (tracing & INPUT_BIT) { (void) fprintf(stderr, "Unknown hop limit from %s\n", buf1); } return; } rip_input(&from, len, *(uint_t *)hopcntopt, ifp); } /* * Process a newly received packet. */ static void rip_input(struct sockaddr_in6 *from, int size, uint_t hopcount, struct interface *ifp) { struct rt_entry *rt; struct netinfo6 *n; int newsize; boolean_t changes = _B_FALSE; int answer = supplier; struct in6_addr prefix; struct in6_addr nexthop; struct in6_addr *gate; boolean_t foundnexthop = _B_FALSE; struct sioc_addrreq sa; struct sockaddr_in6 *sin6; TRACE_INPUT(ifp, from, size); if (tracing & INPUT_BIT) { (void) inet_ntop(from->sin6_family, (void *)&from->sin6_addr, buf1, sizeof (buf1)); } /* * If the packet is recevied on an interface with IFF_NORTEXCH flag set, * we ignore the packet. */ if (ifp->int_flags & RIP6_IFF_NORTEXCH) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Ignore received RIPng packet on %s " "(no route exchange on interface)\n", ifp->int_name); (void) fflush(ftrace); } return; } if (msg->rip6_vers != RIPVERSION6) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad version number %d in packet from %s\n", msg->rip6_vers, buf1); (void) fflush(ftrace); } return; } if (ntohs(msg->rip6_res1) != 0) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Non-zero reserved octets found in packet from " "%s\n", buf1); (void) fflush(ftrace); } } switch (msg->rip6_cmd) { case RIPCMD6_REQUEST: /* multicasted request */ ifp->int_ipackets++; newsize = 0; /* * Adjust size by the length of the command, version and * reserved fields (which are in total 32-bit aligned). */ size -= sizeof (msg->rip6_cmd) + sizeof (msg->rip6_vers) + sizeof (msg->rip6_res1); /* * From section 2.4.1 of RFC 2080: * * If there is exactly one entry in the request with a * destination prefix of zero, a prefix length of zero and * an infinite metric, then supply the entire routing * table. */ n = msg->rip6_nets; if (size == sizeof (struct netinfo6) && n->rip6_prefix_length == 0 && n->rip6_metric == HOPCNT_INFINITY) { rtcreate_prefix(&n->rip6_prefix, &prefix, n->rip6_prefix_length); if (IN6_IS_ADDR_UNSPECIFIED(&prefix)) { supply(from, ifp, 0, from->sin6_port == rip6_port); return; } } for (; size >= sizeof (struct netinfo6); size -= sizeof (struct netinfo6), n++) { if (n->rip6_prefix_length > IPV6_ABITS) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad prefix length %d in request " "from %s\n", n->rip6_prefix_length, buf1); (void) fflush(ftrace); } continue; } if (IN6_IS_ADDR_LINKLOCAL(&n->rip6_prefix) || IN6_IS_ADDR_MULTICAST(&n->rip6_prefix)) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad prefix %s in request from " "%s\n", inet_ntop(AF_INET6, (void *)&n->rip6_prefix, buf2, sizeof (buf2)), buf1); (void) fflush(ftrace); } continue; } rtcreate_prefix(&n->rip6_prefix, &prefix, n->rip6_prefix_length); rt = rtlookup(&prefix, n->rip6_prefix_length); n->rip6_metric = (rt == NULL ? HOPCNT_INFINITY : min(rt->rt_metric, HOPCNT_INFINITY)); newsize += sizeof (struct netinfo6); } if (size > 0) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Ignoring %d octets of trailing data in " "request from %s\n", size, buf1); (void) fflush(ftrace); } } if (answer && newsize > 0) { /* * Adjust newsize by the length of the command, version * and reserved fields (which are in total 32-bit * aligned). */ msg->rip6_cmd = RIPCMD6_RESPONSE; newsize += sizeof (msg->rip6_cmd) + sizeof (msg->rip6_vers) + sizeof (msg->rip6_res1); sendpacket(from, ifp, newsize, 0); } return; case RIPCMD6_RESPONSE: if (hopcount != IPV6_MAX_HOPS) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad hop count %d in response from %s\n", hopcount, buf1); (void) fflush(ftrace); } return; } if (from->sin6_port != rip6_port) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad source port %d in response from %s\n", from->sin6_port, buf1); (void) fflush(ftrace); } return; } if (!IN6_IS_ADDR_LINKLOCAL(&from->sin6_addr)) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad source address (not link-local) in " "response from %s\n", buf1); (void) fflush(ftrace); } return; } ifp->int_ipackets++; /* * Adjust size by the length of the command, version and * reserved fields (which are in total 32-bit aligned). */ size -= sizeof (msg->rip6_cmd) + sizeof (msg->rip6_vers) + sizeof (msg->rip6_res1); for (n = msg->rip6_nets; supplier && size >= sizeof (struct netinfo6); size -= sizeof (struct netinfo6), n++) { /* * From section 2.1.1 of RFC 2080: * * This is a next hop RTE if n->rip6_metric is set to * HOPCNT_NEXTHOP. If the next hop address (which is * placed in the prefix field of this special RTE) is * unspecified or is not a link-local address, then use * the originator's address instead (effectively turning * off next hop RTE processing.) */ if (n->rip6_metric == HOPCNT_NEXTHOP) { /* * First check to see if the unspecified address * was given as the next hop address. This is * the correct way of specifying the end of use * of a next hop address. */ if (IN6_IS_ADDR_UNSPECIFIED(&n->rip6_prefix)) { foundnexthop = _B_FALSE; continue; } /* * A next hop address that is not a link-local * address is treated as the unspecified one. * Trace this event if input tracing is enabled. */ if (!IN6_IS_ADDR_LINKLOCAL(&n->rip6_prefix)) { foundnexthop = _B_FALSE; if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad next hop %s in " "response from %s\n", inet_ntop(AF_INET6, (void *)&n->rip6_prefix, buf2, sizeof (buf2)), buf1); } continue; } /* * Verify that the next hop address is not one * of our own. */ sin6 = (struct sockaddr_in6 *)&sa.sa_addr; sin6->sin6_family = AF_INET6; sin6->sin6_addr = n->rip6_prefix; if (ioctl(iocsoc, SIOCTMYADDR, (char *)&sa) < 0) { syslog(LOG_ERR, "rip_input: " "ioctl (verify my address): %m"); return; } if (sa.sa_res != 0) { foundnexthop = _B_FALSE; if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad next hop %s is self " "in response from %s\n", inet_ntop(AF_INET6, (void *)&n->rip6_prefix, buf2, sizeof (buf2)), buf1); } continue; } foundnexthop = _B_TRUE; nexthop = n->rip6_prefix; continue; } if (foundnexthop) gate = &nexthop; else gate = &from->sin6_addr; if (n->rip6_metric > HOPCNT_INFINITY || n->rip6_metric < 1) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad metric %d in response from " "%s\n", n->rip6_metric, buf1); (void) fflush(ftrace); } continue; } if (n->rip6_prefix_length > IPV6_ABITS) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad prefix length %d in response " "from %s\n", n->rip6_prefix_length, buf1); (void) fflush(ftrace); } continue; } if (IN6_IS_ADDR_LINKLOCAL(&n->rip6_prefix) || IN6_IS_ADDR_MULTICAST(&n->rip6_prefix)) { if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad prefix %s in response from " "%s\n", inet_ntop(AF_INET6, (void *)&n->rip6_prefix, buf2, sizeof (buf2)), buf1); (void) fflush(ftrace); } continue; } /* Include metric for incoming interface */ n->rip6_metric += IFMETRIC(ifp); rtcreate_prefix(&n->rip6_prefix, &prefix, n->rip6_prefix_length); rt = rtlookup(&prefix, n->rip6_prefix_length); if (rt == NULL) { if (n->rip6_metric < HOPCNT_INFINITY) { rtadd(&prefix, gate, n->rip6_prefix_length, n->rip6_metric, n->rip6_route_tag, _B_FALSE, ifp); changes = _B_TRUE; } continue; } /* * If the supplied metric is at least HOPCNT_INFINITY * and the current metric of the route is * HOPCNT_INFINITY, then this particular RTE is ignored. */ if (n->rip6_metric >= HOPCNT_INFINITY && rt->rt_metric == HOPCNT_INFINITY) continue; /* * From section 2.4.2 of RFC 2080: * * Update if any one of the following is true * * 1) From current gateway and a different metric. * 2) From current gateway and a different index. * 3) A shorter (smaller) metric. * 4) Equivalent metric and an age at least * one-half of EXPIRE_TIME. * * Otherwise, update timer for the interface on which * the packet arrived. */ if (IN6_ARE_ADDR_EQUAL(gate, &rt->rt_router)) { if (n->rip6_metric != rt->rt_metric || rt->rt_ifp != ifp) { rtchange(rt, gate, n->rip6_metric, ifp); changes = _B_TRUE; } else if (n->rip6_metric < HOPCNT_INFINITY) { rt->rt_timer = 0; } } else if (n->rip6_metric < rt->rt_metric || (rt->rt_timer > (EXPIRE_TIME / 2) && rt->rt_metric == n->rip6_metric)) { rtchange(rt, gate, n->rip6_metric, ifp); changes = _B_TRUE; } } if (changes && supplier) dynamic_update(ifp); return; default: if (tracing & INPUT_BIT) { (void) fprintf(ftrace, "Bad command %d in packet from %s\n", msg->rip6_cmd, buf1); (void) fflush(ftrace); } return; } } /* * If changes have occurred, and if we have not sent a multicast * recently, send a dynamic update. This update is sent only * on interfaces other than the one on which we received notice * of the change. If we are within MIN_WAIT_TIME of a full update, * don't bother sending; if we just sent a dynamic update * and set a timer (nextmcast), delay until that time. * If we just sent a full update, delay the dynamic update. * Set a timer for a randomized value to suppress additional * dynamic updates until it expires; if we delayed sending * the current changes, set needupdate. */ void dynamic_update(struct interface *ifp) { int delay; if (now.tv_sec - lastfullupdate.tv_sec >= supplyinterval - MIN_WAIT_TIME) return; if (now.tv_sec - lastmcast.tv_sec >= MIN_WAIT_TIME && /* BEGIN CSTYLED */ timercmp(&nextmcast, &now, <)) { /* END CSTYLED */ TRACE_ACTION("send dynamic update", (struct rt_entry *)NULL); supplyall(&allrouters, RTS_CHANGED, ifp, _B_TRUE); lastmcast = now; needupdate = _B_FALSE; nextmcast.tv_sec = 0; } else { needupdate = _B_TRUE; TRACE_ACTION("delay dynamic update", (struct rt_entry *)NULL); } if (nextmcast.tv_sec == 0) { delay = GET_RANDOM(MIN_WAIT_TIME * 1000000, MAX_WAIT_TIME * 1000000); if (tracing & ACTION_BIT) { (void) fprintf(ftrace, "inhibit dynamic update for %d msec\n", delay / 1000); (void) fflush(ftrace); } nextmcast.tv_sec = delay / 1000000; nextmcast.tv_usec = delay % 1000000; timevaladd(&nextmcast, &now); /* * If the next possibly dynamic update * is within MIN_WAIT_TIME of the next full * update, force the delay past the full * update, or we might send a dynamic update * just before the full update. */ if (nextmcast.tv_sec > lastfullupdate.tv_sec + supplyinterval - MIN_WAIT_TIME) { nextmcast.tv_sec = lastfullupdate.tv_sec + supplyinterval + 1; } } } /* * 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 1999 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #ident "%Z%%M% %I% %E% SMI" struct interface { struct interface *int_next; struct in6_addr int_addr; /* address on this if */ struct in6_addr int_dstaddr; /* other end of p-to-p link */ int int_metric; /* init's routing entry */ uint_t int_flags; /* see below */ int int_prefix_length; /* prefix length on this if */ char *int_name; /* from kernel if structure */ char *int_ifbase; /* name of physical interface */ int int_sock; /* socket on if to send/recv */ int int_ifindex; /* interface index */ uint_t int_mtu; /* maximum transmission unit */ struct ifdebug int_input, int_output; /* packet tracing stuff */ int int_ipackets; /* input packets received */ int int_opackets; /* output packets sent */ ushort_t int_transitions; /* times gone up-down */ }; #define RIP6_IFF_UP 0x1 /* interface is up */ #define RIP6_IFF_POINTOPOINT 0x2 /* interface is p-to-p link */ #define RIP6_IFF_MARKED 0x4 /* to determine removed ifs */ #define RIP6_IFF_NORTEXCH 0x8 /* don't exchange route info */ #define RIP6_IFF_PRIVATE 0x10 /* interface is private */ #define IFMETRIC(ifp) ((ifp != NULL) ? (ifp)->int_metric : 1) extern void if_dump(void); extern struct interface *if_ifwithname(char *); extern void if_purge(struct interface *); /* * 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 2005 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include "defs.h" struct sockaddr_in6 allrouters; char *control; boolean_t dopoison = _B_TRUE; /* Do poison reverse */ int iocsoc; struct timeval lastfullupdate; /* last time full table multicast */ struct timeval lastmcast; /* last time all/changes multicast */ int max_poll_ifs = START_POLL_SIZE; struct rip6 *msg; boolean_t needupdate; /* true if need update at nextmcast */ struct timeval nextmcast; /* time to wait before changes mcast */ struct timeval now; /* current idea of time */ char *packet; struct pollfd *poll_ifs = NULL; int poll_ifs_num = 0; int rip6_port; boolean_t supplier = _B_TRUE; /* process should supply updates */ struct in6_addr allrouters_in6 = { /* BEGIN CSTYLED */ { 0xff, 0x2, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x9 } /* END CSTYLED */ }; static void timevalsub(struct timeval *t1, struct timeval *t2); static void usage(char *fname) { (void) fprintf(stderr, "usage: " "%s [ -P ] [ -p port ] [ -q ] [ -s ] [ -t ] [ -v ] []\n", fname); exit(EXIT_FAILURE); } int main(int argc, char *argv[]) { int i, n; struct interface *ifp; int c; struct timeval waittime; int timeout; boolean_t daemon = _B_TRUE; /* Fork off a detached daemon */ FILE *pidfp; mode_t pidmode = (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH); /* 0644 */ rip6_port = htons(IPPORT_ROUTESERVER6); allrouters.sin6_family = AF_INET6; allrouters.sin6_port = rip6_port; allrouters.sin6_addr = allrouters_in6; while ((c = getopt(argc, argv, "nsqvTtdgPp:")) != EOF) { switch (c) { case 'n': install = _B_FALSE; break; case 's': supplier = _B_TRUE; break; case 'q': supplier = _B_FALSE; break; case 'v': tracing |= ACTION_BIT; break; case 'T': daemon = _B_FALSE; break; case 't': tracepackets = _B_TRUE; daemon = _B_FALSE; tracing |= (INPUT_BIT | OUTPUT_BIT); break; case 'd': break; case 'P': dopoison = _B_FALSE; break; case 'p': rip6_port = htons(atoi(optarg)); allrouters.sin6_port = rip6_port; break; default: usage(argv[0]); /* NOTREACHED */ } } /* * Any extra argument is considered * a tracing log file. */ if (optind < argc) { traceon(argv[optind]); } else if (tracing && !daemon) { traceonfp(stdout); } else if (tracing) { (void) fprintf(stderr, "Need logfile with -v\n"); usage(argv[0]); /* NOTREACHED */ } if (daemon) { int t; if (fork()) exit(EXIT_SUCCESS); for (t = 0; t < 20; t++) { if (!tracing || (t != fileno(ftrace))) (void) close(t); } (void) open("/", 0); (void) dup2(0, 1); (void) dup2(0, 2); (void) setsid(); } /* Store our process id, blow away any existing file if it exists. */ if ((pidfp = fopen(PATH_PID, "w")) == NULL) { (void) fprintf(stderr, "%s: unable to open " PATH_PID ": %s\n", argv[0], strerror(errno)); } else { (void) fprintf(pidfp, "%ld\n", getpid()); (void) fclose(pidfp); (void) chmod(PATH_PID, pidmode); } iocsoc = socket(AF_INET6, SOCK_DGRAM, 0); if (iocsoc < 0) { syslog(LOG_ERR, "main: socket: %m"); exit(EXIT_FAILURE); } setup_rtsock(); /* * Allocate the buffer to hold the RIPng packet. In reality, it will be * smaller than IPV6_MAX_PACKET octets due to (at least) the IPv6 and * UDP headers but IPV6_MAX_PACKET is a convenient size. */ packet = (char *)malloc(IPV6_MAX_PACKET); if (packet == NULL) { syslog(LOG_ERR, "main: malloc: %m"); exit(EXIT_FAILURE); } msg = (struct rip6 *)packet; /* * Allocate the buffer to hold the ancillary data. This data is used to * insure that the incoming hop count of a RIPCMD6_RESPONSE message is * IPV6_MAX_HOPS which indicates that it came from a direct neighbor * (namely, no intervening router decremented it). */ control = (char *)malloc(IPV6_MAX_PACKET); if (control == NULL) { syslog(LOG_ERR, "main: malloc: %m"); exit(EXIT_FAILURE); } openlog("in.ripngd", LOG_PID | LOG_CONS, LOG_DAEMON); (void) gettimeofday(&now, (struct timezone *)NULL); initifs(); solicitall(&allrouters); if (supplier) supplyall(&allrouters, 0, (struct interface *)NULL, _B_TRUE); (void) sigset(SIGALRM, (void (*)(int))timer); (void) sigset(SIGHUP, (void (*)(int))initifs); (void) sigset(SIGTERM, (void (*)(int))term); (void) sigset(SIGUSR1, (void (*)(int))if_dump); (void) sigset(SIGUSR2, (void (*)(int))rtdump); /* * Seed the pseudo-random number generator for GET_RANDOM(). */ srandom((uint_t)gethostid()); timer(); for (;;) { if (needupdate) { waittime = nextmcast; timevalsub(&waittime, &now); if (waittime.tv_sec < 0) { timeout = 0; } else { timeout = TIME_TO_MSECS(waittime); } if (tracing & ACTION_BIT) { (void) fprintf(ftrace, "poll until dynamic update in %d msec\n", timeout); (void) fflush(ftrace); } } else { timeout = INFTIM; } if ((n = poll(poll_ifs, poll_ifs_num, timeout)) < 0) { if (errno == EINTR) continue; syslog(LOG_ERR, "main: poll: %m"); exit(EXIT_FAILURE); } (void) sighold(SIGALRM); (void) sighold(SIGHUP); /* * Poll timed out. */ if (n == 0) { if (needupdate) { TRACE_ACTION("send delayed dynamic update", (struct rt_entry *)NULL); (void) gettimeofday(&now, (struct timezone *)NULL); supplyall(&allrouters, RTS_CHANGED, (struct interface *)NULL, _B_TRUE); lastmcast = now; needupdate = _B_FALSE; nextmcast.tv_sec = 0; } (void) sigrelse(SIGHUP); (void) sigrelse(SIGALRM); continue; } (void) gettimeofday(&now, (struct timezone *)NULL); for (i = 0; i < poll_ifs_num; i++) { /* * This case should never happen. */ if (poll_ifs[i].revents & POLLERR) { syslog(LOG_ERR, "main: poll returned a POLLERR event"); continue; } if (poll_ifs[i].revents & POLLIN) { for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) { if (poll_ifs[i].fd == ifp->int_sock) in_data(ifp); } } } (void) sigrelse(SIGHUP); (void) sigrelse(SIGALRM); } return (0); } void timevaladd(struct timeval *t1, struct timeval *t2) { t1->tv_sec += t2->tv_sec; if ((t1->tv_usec += t2->tv_usec) > 1000000) { t1->tv_sec++; t1->tv_usec -= 1000000; } } void timevalsub(struct timeval *t1, struct timeval *t2) { t1->tv_sec -= t2->tv_sec; if ((t1->tv_usec -= t2->tv_usec) < 0) { t1->tv_sec--; t1->tv_usec += 1000000; } } /* * 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 1999 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #ident "%Z%%M% %I% %E% SMI" /* SVr4.0 1.1 */ /* * Routing Table Management Daemon */ #include "defs.h" /* * Apply the function "supply" to all active * interfaces with a link-local address. */ void supplyall(struct sockaddr_in6 *sin6, int rtstate, struct interface *skipif, boolean_t splith) { struct interface *ifp; for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) { if ((ifp->int_flags & RIP6_IFF_UP) == 0) continue; if (ifp->int_flags & RIP6_IFF_NORTEXCH) { if (tracing & OUTPUT_BIT) { (void) fprintf(ftrace, "Suppress sending RIPng response packet " "on %s (no route exchange on interface)\n", ifp->int_name); (void) fflush(ftrace); } continue; } if (ifp->int_sock == -1) continue; if (ifp == skipif) continue; if (!IN6_IS_ADDR_LINKLOCAL(&ifp->int_addr)) continue; supply(sin6, ifp, rtstate, splith); } } static void solicit(struct sockaddr_in6 *sin6, struct interface *ifp) { msg->rip6_cmd = RIPCMD6_REQUEST; msg->rip6_vers = RIPVERSION6; msg->rip6_nets[0].rip6_prefix = in6addr_any; msg->rip6_nets[0].rip6_prefix_length = 0; msg->rip6_nets[0].rip6_metric = HOPCNT_INFINITY; sendpacket(sin6, ifp, sizeof (struct rip6), 0); } void solicitall(struct sockaddr_in6 *sin6) { struct interface *ifp; for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) { if ((ifp->int_flags & RIP6_IFF_UP) == 0) continue; if (ifp->int_flags & RIP6_IFF_NORTEXCH) { if (tracing & OUTPUT_BIT) { (void) fprintf(ftrace, "Suppress sending RIPng request packet " "on %s (no route exchange on interface)\n", ifp->int_name); (void) fflush(ftrace); } continue; } if (ifp->int_sock == -1) continue; solicit(sin6, ifp); } } /* * Output a preformed packet. */ /*ARGSUSED*/ void sendpacket(struct sockaddr_in6 *sin6, struct interface *ifp, int size, int flags) { if (sendto(ifp->int_sock, packet, size, flags, (struct sockaddr *)sin6, sizeof (*sin6)) < 0) { syslog(LOG_ERR, "sendpacket: sendto: %m"); return; } TRACE_OUTPUT(ifp, sin6, sizeof (struct rip6)); ifp->int_opackets++; } /* * Supply dst with the contents of the routing tables. * If this won't fit in one packet, chop it up into several. */ void supply(struct sockaddr_in6 *sin6, struct interface *ifp, int rtstate, boolean_t splith) { struct rt_entry *rt; struct netinfo6 *n = msg->rip6_nets; struct rthash *rh; int size, i, maxsize; uint8_t rtmetric; msg->rip6_cmd = RIPCMD6_RESPONSE; msg->rip6_vers = RIPVERSION6; /* * Initialize maxsize to the size of the largest RIPng packet supported * on the outgoing interface. */ maxsize = ifp->int_mtu - sizeof (ip6_t) - sizeof (struct udphdr); for (i = IPV6_ABITS; i >= 0; i--) { if (net_hashes[i] == NULL) continue; for (rh = net_hashes[i]; rh < &net_hashes[i][ROUTEHASHSIZ]; rh++) { for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw) { if (IN6_IS_ADDR_LINKLOCAL(&rt->rt_dst)) continue; if (IN6_IS_ADDR_UNSPECIFIED(&rt->rt_dst)) continue; /* do not send if private */ if (rt->rt_state & RTS_PRIVATE) continue; /* * Don't resend the information * on the network from which it was received. */ if (splith && rt->rt_ifp != NULL && strcmp(ifp->int_ifbase, rt->rt_ifp->int_ifbase) == 0) { if (dopoison) rtmetric = HOPCNT_INFINITY; else continue; } else { rtmetric = rt->rt_metric; } /* * For dynamic updates, limit update to routes * with the specified state. */ if (rtstate != 0 && (rt->rt_state & rtstate) == 0) continue; /* * Check if there is space for another RTE. If * not, send the packet built up and reset n for * the remaining RTEs. */ size = (char *)n - packet; if (size > maxsize - sizeof (struct netinfo6)) { sendpacket(sin6, ifp, size, 0); TRACE_OUTPUT(ifp, sin6, size); n = msg->rip6_nets; } n->rip6_prefix = rt->rt_dst; n->rip6_route_tag = rt->rt_tag; n->rip6_prefix_length = rt->rt_prefix_length; n->rip6_metric = min(rtmetric, HOPCNT_INFINITY); n++; } /* end of hash chain */ } /* end of particular prefix length */ } /* end of all prefix lengths */ if (n != msg->rip6_nets) { size = (char *)n - packet; sendpacket(sin6, ifp, size, 0); TRACE_OUTPUT(ifp, sin6, size); } } /* * 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 2002 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ #include "defs.h" #define IF_SEPARATOR ':' struct interface *ifnet; static int setup_listen_sock(int ifindex); static void addrouteforif(struct interface *ifp); static void resetup_listen_sock(struct interface *, int); /* * This is called at startup and after that, every CHECK_INTERVAL seconds or * when a SIGHUP is received. */ void initifs(void) { static char *buf = NULL; static uint_t maxbufsize = 0; int bufsize; int numifs; struct lifnum lifn; struct lifconf lifc; struct lifreq lifr; struct lifreq *lifrp; int n; struct interface ifs; struct interface *ifp; int netmaskchange = 0; boolean_t changes = _B_FALSE; lifn.lifn_family = AF_INET6; lifn.lifn_flags = 0; if (ioctl(iocsoc, SIOCGLIFNUM, (char *)&lifn) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface numbers): %m"); return; } numifs = lifn.lifn_count; bufsize = numifs * sizeof (struct lifreq); if (buf == NULL || bufsize > maxbufsize) { if (buf != NULL) free(buf); maxbufsize = bufsize; buf = (char *)malloc(maxbufsize); if (buf == NULL) { syslog(LOG_ERR, "initifs: out of memory"); return; } } lifc.lifc_family = AF_INET6; lifc.lifc_flags = 0; lifc.lifc_len = bufsize; lifc.lifc_buf = buf; if (ioctl(iocsoc, SIOCGLIFCONF, (char *)&lifc) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface configuration): %m"); return; } /* * Mark all of the currently known interfaces in order to determine * which of the these interfaces no longer exist. */ for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) ifp->int_flags |= RIP6_IFF_MARKED; lifrp = lifc.lifc_req; for (n = lifc.lifc_len / sizeof (struct lifreq); n > 0; n--, lifrp++) { bzero((char *)&ifs, sizeof (ifs)); (void) strncpy(lifr.lifr_name, lifrp->lifr_name, sizeof (lifr.lifr_name)); if (ioctl(iocsoc, SIOCGLIFFLAGS, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface flags): %m"); continue; } if (!(lifr.lifr_flags & IFF_IPV6) || !(lifr.lifr_flags & IFF_MULTICAST) || (lifr.lifr_flags & IFF_LOOPBACK)) continue; ifp = if_ifwithname(lifr.lifr_name); if (ifp != NULL) ifp->int_flags &= ~RIP6_IFF_MARKED; if (lifr.lifr_flags & IFF_POINTOPOINT) ifs.int_flags |= RIP6_IFF_POINTOPOINT; if (lifr.lifr_flags & IFF_NORTEXCH) ifs.int_flags |= RIP6_IFF_NORTEXCH; if (lifr.lifr_flags & IFF_PRIVATE) ifs.int_flags |= RIP6_IFF_PRIVATE; if (lifr.lifr_flags & IFF_UP) { ifs.int_flags |= RIP6_IFF_UP; } else { if (ifp != NULL) { if (ifp->int_flags & RIP6_IFF_UP) { /* * If there is an transition from up to * down for an exisiting interface, * increment the counter. */ ifp->int_transitions++; changes = _B_TRUE; } if_purge(ifp); } continue; } if (ifs.int_flags & RIP6_IFF_POINTOPOINT) { /* * For point-to-point interfaces, retrieve both the * local and the remote addresses. */ if (ioctl(iocsoc, SIOCGLIFADDR, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get interface address): " "%m"); continue; } ifs.int_addr = ((struct sockaddr_in6 *)&lifr.lifr_addr)->sin6_addr; if (ioctl(iocsoc, SIOCGLIFDSTADDR, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get destination address): " "%m"); continue; } ifs.int_dstaddr = ((struct sockaddr_in6 *) &lifr.lifr_dstaddr)->sin6_addr; ifs.int_prefix_length = IPV6_ABITS; } else { /* * For other interfaces, retreieve the prefix (including * the prefix length. */ if (ioctl(iocsoc, SIOCGLIFSUBNET, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get subnet prefix): %m"); continue; } /* * This should never happen but check for it in any case * since the kernel stores it as an signed integer. */ if (lifr.lifr_addrlen < 0 || lifr.lifr_addrlen > IPV6_ABITS) { syslog(LOG_ERR, "initifs: ioctl (get subnet prefix) " "returned invalid prefix length of %d", lifr.lifr_addrlen); continue; } ifs.int_prefix_length = lifr.lifr_addrlen; ifs.int_addr = ((struct sockaddr_in6 *) &lifr.lifr_subnet)->sin6_addr; } if (ioctl(iocsoc, SIOCGLIFMETRIC, (char *)&lifr) < 0 || lifr.lifr_metric < 0) ifs.int_metric = 1; else ifs.int_metric = lifr.lifr_metric + 1; if (ioctl(iocsoc, SIOCGLIFINDEX, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get index): %m"); continue; } ifs.int_ifindex = lifr.lifr_index; if (ioctl(iocsoc, SIOCGLIFMTU, (char *)&lifr) < 0) { syslog(LOG_ERR, "initifs: ioctl (get mtu): %m"); continue; } /* * If the interface's recorded MTU doesn't make sense, use * IPV6_MIN_MTU instead. */ if (lifr.lifr_mtu < IPV6_MIN_MTU) ifs.int_mtu = IPV6_MIN_MTU; else ifs.int_mtu = lifr.lifr_mtu; if (ifp != NULL) { /* * RIP6_IFF_NORTEXCH flag change by itself shouldn't * cause an if_purge() call, which also purges all the * routes heard off this interface. So, let's suppress * changes of RIP6_IFF_NORTEXCH in the following * comparisons. */ if (ifp->int_prefix_length == ifs.int_prefix_length && ((ifp->int_flags | RIP6_IFF_NORTEXCH) == (ifs.int_flags | RIP6_IFF_NORTEXCH)) && ifp->int_metric == ifs.int_metric && ifp->int_ifindex == ifs.int_ifindex) { /* * Now let's make sure we capture the latest * value of RIP6_IFF_NORTEXCH flag. */ if (ifs.int_flags & RIP6_IFF_NORTEXCH) ifp->int_flags |= RIP6_IFF_NORTEXCH; else ifp->int_flags &= ~RIP6_IFF_NORTEXCH; if (!(ifp->int_flags & RIP6_IFF_POINTOPOINT) && IN6_ARE_ADDR_EQUAL(&ifp->int_addr, &ifs.int_addr)) continue; if ((ifp->int_flags & RIP6_IFF_POINTOPOINT) && IN6_ARE_ADDR_EQUAL(&ifp->int_dstaddr, &ifs.int_dstaddr)) continue; } if_purge(ifp); if (ifp->int_prefix_length != ifs.int_prefix_length) netmaskchange = 1; ifp->int_addr = ifs.int_addr; ifp->int_dstaddr = ifs.int_dstaddr; ifp->int_metric = ifs.int_metric; /* * If there is an transition from down to up for an * exisiting interface, increment the counter. */ if (!(ifp->int_flags & RIP6_IFF_UP) && (ifs.int_flags & RIP6_IFF_UP)) ifp->int_transitions++; ifp->int_flags |= ifs.int_flags; ifp->int_prefix_length = ifs.int_prefix_length; /* * If the interface index has changed, we may need to * set up the listen socket again. */ if (ifp->int_ifindex != ifs.int_ifindex) { if (ifp->int_sock != -1) { resetup_listen_sock(ifp, ifs.int_ifindex); } ifp->int_ifindex = ifs.int_ifindex; } ifp->int_mtu = ifs.int_mtu; } else { char *cp; int log_num; ifp = (struct interface *) malloc(sizeof (struct interface)); if (ifp == NULL) { syslog(LOG_ERR, "initifs: out of memory"); return; } *ifp = ifs; ifp->int_name = ifp->int_ifbase = NULL; ifp->int_name = (char *)malloc((size_t)strlen(lifr.lifr_name) + 1); if (ifp->int_name == NULL) { free(ifp); syslog(LOG_ERR, "initifs: out of memory"); return; } (void) strcpy(ifp->int_name, lifr.lifr_name); ifp->int_ifbase = (char *)malloc((size_t)strlen(lifr.lifr_name) + 1); if (ifp->int_ifbase == NULL) { free(ifp->int_name); free(ifp); syslog(LOG_ERR, "initifs: out of memory"); return; } (void) strcpy(ifp->int_ifbase, lifr.lifr_name); cp = (char *)index(ifp->int_ifbase, IF_SEPARATOR); if (cp != NULL) { /* * Verify that the value following the separator * is an integer greater than zero (the only * possible value for a logical interface). */ log_num = atoi((char *)(cp + 1)); if (log_num <= 0) { free(ifp->int_ifbase); free(ifp->int_name); free(ifp); syslog(LOG_ERR, "initifs: interface name %s could " "not be parsed", ifp->int_name); return; } *cp = '\0'; } else { log_num = 0; } if (log_num == 0) { ifp->int_sock = setup_listen_sock(ifp->int_ifindex); } else { ifp->int_sock = -1; } ifp->int_next = ifnet; ifnet = ifp; traceinit(ifp); } addrouteforif(ifp); changes = _B_TRUE; } /* * Any remaining interfaces that are still marked and which were in an * up state (RIP6_IFF_UP) need to removed from the routing table. */ for (ifp = ifnet; ifp != NULL; ifp = ifp->int_next) { if ((ifp->int_flags & (RIP6_IFF_MARKED | RIP6_IFF_UP)) == (RIP6_IFF_MARKED | RIP6_IFF_UP)) { if_purge(ifp); ifp->int_flags &= ~RIP6_IFF_MARKED; changes = _B_TRUE; } } if (netmaskchange) rtchangeall(); if (supplier & changes) dynamic_update((struct interface *)NULL); } static void addrouteforif(struct interface *ifp) { struct rt_entry *rt; struct in6_addr *dst; if (ifp->int_flags & RIP6_IFF_POINTOPOINT) dst = &ifp->int_dstaddr; else dst = &ifp->int_addr; rt = rtlookup(dst, ifp->int_prefix_length); if (rt != NULL) { if (rt->rt_state & RTS_INTERFACE) return; rtdelete(rt); } rtadd(dst, &ifp->int_addr, ifp->int_prefix_length, ifp->int_metric, 0, _B_TRUE, ifp); } static int setup_listen_sock(int ifindex) { int sock; struct sockaddr_in6 sin6; uint_t hops; struct ipv6_mreq allrouters_mreq; int on = 1; int off = 0; int recvsize; sock = socket(AF_INET6, SOCK_DGRAM, 0); if (sock == -1) goto sock_fail; if (setsockopt(sock, IPPROTO_IPV6, IPV6_BOUND_IF, (char *)&ifindex, sizeof (ifindex)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_BOUND_IF: %m"); goto sock_fail; } hops = IPV6_MAX_HOPS; if (setsockopt(sock, IPPROTO_IPV6, IPV6_UNICAST_HOPS, (char *)&hops, sizeof (hops)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_UNICAST_HOPS: %m"); goto sock_fail; } if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_HOPS, (char *)&hops, sizeof (hops)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_MULTICAST_HOPS: %m"); goto sock_fail; } if (setsockopt(sock, IPPROTO_IPV6, IPV6_MULTICAST_LOOP, (char *)&off, sizeof (off)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_MULTICAST_LOOP: %m"); goto sock_fail; } allrouters_mreq.ipv6mr_multiaddr = allrouters_in6; allrouters_mreq.ipv6mr_interface = ifindex; if (setsockopt(sock, IPPROTO_IPV6, IPV6_JOIN_GROUP, (char *)&allrouters_mreq, sizeof (allrouters_mreq)) < 0) { if (errno != EADDRINUSE) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: " "IPV6_JOIN_GROUP: %m"); goto sock_fail; } } if (setsockopt(sock, IPPROTO_IPV6, IPV6_RECVHOPLIMIT, (char *)&on, sizeof (off)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: IPV6_RECVHOPLIMIT: %m"); goto sock_fail; } if (setsockopt(sock, SOL_SOCKET, SO_REUSEADDR, (char *)&on, sizeof (on)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: SO_REUSEADDR: %m"); goto sock_fail; } recvsize = RCVBUFSIZ; if (setsockopt(sock, SOL_SOCKET, SO_RCVBUF, (char *)&recvsize, sizeof (int)) < 0) { syslog(LOG_ERR, "setup_listen_sock: setsockopt: SO_RCVBUF: %m"); goto sock_fail; } bzero((char *)&sin6, sizeof (sin6)); sin6.sin6_family = AF_INET6; sin6.sin6_port = rip6_port; if (bind(sock, (struct sockaddr *)&sin6, sizeof (sin6)) < 0) { syslog(LOG_ERR, "setup_listen_sock: bind: %m"); goto sock_fail; } poll_ifs_num++; if (poll_ifs == NULL) { poll_ifs = (struct pollfd *) malloc(max_poll_ifs * sizeof (struct pollfd)); } else if (poll_ifs_num > max_poll_ifs) { max_poll_ifs *= 2; poll_ifs = (struct pollfd *)realloc((char *)poll_ifs, max_poll_ifs * sizeof (struct pollfd)); } if (poll_ifs == NULL) { syslog(LOG_ERR, "setup_listen_sock: out of memory"); goto sock_fail; } poll_ifs[poll_ifs_num - 1].fd = sock; poll_ifs[poll_ifs_num - 1].events = POLLIN; return (sock); sock_fail: if (sock > 0) (void) close(sock); return (-1); } /* * resetup_listen_sock is primarily used in the case where a tunnel was * plumbed, unplumbed, then plumbed again. This would cause the binding set by * IPV6_BOUND_IF to be useless, and sends to the associated socket will be * transmitted on the wrong interface. resetup_listen_sock * closes the socket, * removes the socket from poll_ifs[] * plugs the hole in poll_ifs[] * calls setup_listen_sock to set up the socket again */ void resetup_listen_sock(struct interface *ifp, int newindex) { int i; (void) close(ifp->int_sock); /* Remove socket from poll_ifs[]. */ for (i = poll_ifs_num - 1; i >= 0; i--) { if (poll_ifs[i].fd == ifp->int_sock) { poll_ifs[i].fd = 0; poll_ifs[i].events = 0; /* * Remove hole in poll_ifs. Possibly exchange * poll_ifs[i] with poll_ifs[poll_ifs_num-1]. */ if (i != poll_ifs_num - 1) { poll_ifs[i] = poll_ifs[poll_ifs_num - 1]; poll_ifs[poll_ifs_num - 1].fd = 0; poll_ifs[poll_ifs_num - 1].events = 0; } poll_ifs_num--; /* Now set everything up again. */ ifp->int_sock = setup_listen_sock(newindex); break; } } } #!/sbin/sh # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # ident "%Z%%M% %I% %E% SMI" . /lib/svc/share/smf_include.sh . /lib/svc/share/routing_include.sh smf_configure_ip || exit $SMF_EXIT_OK daemon_args=`get_daemon_args $SMF_FMRI` options="sqp:Ptv" # # Handle upgrade - routing/daemon-args property must be mapped to properties # in routeadm property group. Note the SMF-incompatible -t option is not # supported, since it requires that in.ripngd run in the foreground. # if [ -n "$daemon_args" ]; then set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "q" quiet_mode true false set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "s" supply_routes true false set_daemon_value_property "$SMF_FMRI" "$daemon_args" \ "$options" "p" udp_port 521 set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "P" poison_reverse false true set_daemon_boolean_property "$SMF_FMRI" "$daemon_args" \ "$options" "v" verbose true false set_daemon_nonoption_properties "$SMF_FMRI" "$daemon_args" \ "$options" "log_file" clear_daemon_args $SMF_FMRI fi # # Assemble arguments to daemon from properties # args="`get_daemon_option_from_boolean_property $SMF_FMRI \ quiet_mode -q true`" args="$args `get_daemon_option_from_boolean_property $SMF_FMRI \ supply_routes -s true`" args="$args `get_daemon_option_from_property $SMF_FMRI udp_port p 521`" args="$args `get_daemon_option_from_boolean_property $SMF_FMRI \ poison_reverse -P false`" args="$args `get_daemon_option_from_boolean_property $SMF_FMRI \ verbose -v true`" args="$args `get_daemon_nonoption_property $SMF_FMRI log_file`" /usr/lib/inet/in.ripngd $args [ "$?" = 0 ] || exit $SMF_EXIT_ERR_FATAL exit "$SMF_EXIT_OK" /* * 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 1999 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Routing table management daemon. */ /* * Routing table structure; differs a bit from kernel tables. */ struct rthash { struct rt_entry *rt_forw; struct rt_entry *rt_back; }; struct rt_entry { struct rt_entry *rt_forw; struct rt_entry *rt_back; uint_t rt_hash; /* for net or host */ struct in6_addr rt_dst; /* match value */ struct in6_addr rt_router; /* who to forward to */ int rt_prefix_length; /* bits in prefix */ struct interface *rt_ifp; /* interface to take */ uint_t rt_flags; /* kernel flags */ uint_t rt_state; /* see below */ int rt_timer; /* for invalidation */ int rt_metric; /* cost of route including the if */ int rt_tag; /* route tag attribute */ }; #define ROUTEHASHSIZ 32 /* must be a power of 2 */ #define ROUTEHASHMASK (ROUTEHASHSIZ - 1) /* * "State" of routing table entry. */ #define RTS_CHANGED 0x1 /* route has been altered recently */ #define RTS_INTERFACE 0x2 /* route is for network interface */ #define RTS_PRIVATE 0x4 /* route is private, do not advertise */ /* * XXX This is defined in (but should be defined in * for completeness). */ #define IPV6_ABITS 128 /* Number of bits in an IPv6 address */ extern struct rthash *net_hashes[IPV6_ABITS + 1]; extern void rtadd(struct in6_addr *, struct in6_addr *, int, int, int, boolean_t, struct interface *); extern void rtchange(struct rt_entry *, struct in6_addr *, short, struct interface *); extern void rtchangeall(void); extern void rtcreate_prefix(struct in6_addr *, struct in6_addr *, int); extern void rtdelete(struct rt_entry *); extern void rtdown(struct rt_entry *); extern void rtdump(void); extern struct rt_entry *rtlookup(struct in6_addr *, int); extern void rtpurgeif(struct interface *); /* * 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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Routing Table Management Daemon */ #include "defs.h" boolean_t install = _B_TRUE; /* update kernel routing table */ struct rthash *net_hashes[IPV6_ABITS + 1]; /* * Size of routing socket message used by in.ripngd which includes the header, * space for the RTA_DST, RTA_GATEWAY and RTA_NETMASK (each a sockaddr_in6) * plus space for the RTA_IFP (a sockaddr_dl). */ #define RIPNG_RTM_MSGLEN sizeof (struct rt_msghdr) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_in6) + \ sizeof (struct sockaddr_dl) static int rtmseq; /* rtm_seq sequence number */ static int rtsock; /* Routing socket */ static struct rt_msghdr *rt_msg; /* Routing socket message */ static struct sockaddr_in6 *rta_dst; /* RTA_DST sockaddr */ static struct sockaddr_in6 *rta_gateway; /* RTA_GATEWAY sockaddr */ static struct sockaddr_in6 *rta_netmask; /* RTA_NETMASK sockaddr */ static struct sockaddr_dl *rta_ifp; /* RTA_IFP sockaddr */ /* simulate vax insque and remque instructions. */ typedef struct vq { caddr_t fwd, back; } vq_t; #define insque(e, p) ((vq_t *)(e))->back = (caddr_t)(p); \ ((vq_t *)(e))->fwd = \ (caddr_t)((vq_t *)((vq_t *)(p))->fwd); \ ((vq_t *)((vq_t *)(p))->fwd)->back = (caddr_t)(e); \ ((vq_t *)(p))->fwd = (caddr_t)(e); #define remque(e) ((vq_t *)((vq_t *)(e))->back)->fwd = \ (caddr_t)((vq_t *)(e))->fwd; \ ((vq_t *)((vq_t *)(e))->fwd)->back = \ (caddr_t)((vq_t *)(e))->back; \ ((vq_t *)(e))->fwd = NULL; \ ((vq_t *)(e))->back = NULL; static void log_change(int level, struct rt_entry *orig, struct rt_entry *new) { char buf1[INET6_ADDRSTRLEN]; char buf2[INET6_ADDRSTRLEN]; char buf3[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *) &new->rt_dst, buf1, sizeof (buf1)); (void) inet_ntop(AF_INET6, (void *) &orig->rt_router, buf2, sizeof (buf2)); (void) inet_ntop(AF_INET6, (void *) &new->rt_router, buf3, sizeof (buf3)); syslog(level, "\tdst %s from gw %s if %s to gw %s if %s metric %d", buf1, buf2, (orig->rt_ifp != NULL && orig->rt_ifp->int_name != NULL) ? orig->rt_ifp->int_name : "(noname)", buf3, (new->rt_ifp != NULL && new->rt_ifp->int_name != NULL) ? new->rt_ifp->int_name : "(noname)", new->rt_metric); } static void log_single(int level, struct rt_entry *rt) { char buf1[INET6_ADDRSTRLEN]; char buf2[INET6_ADDRSTRLEN]; (void) inet_ntop(AF_INET6, (void *)&rt->rt_dst, buf1, sizeof (buf1)); (void) inet_ntop(AF_INET6, (void *)&rt->rt_router, buf2, sizeof (buf2)); syslog(level, "\tdst %s gw %s if %s metric %d", buf1, buf2, (rt->rt_ifp != NULL && rt->rt_ifp->int_name != NULL) ? rt->rt_ifp->int_name : "(noname)", rt->rt_metric); } /* * Computes a hash by XOR-ing the (up to sixteen) octets that make up an IPv6 * address. This function assumes that that there are no one-bits in the * address beyond the prefix length. */ static uint8_t rthash(struct in6_addr *dst, int prefix_length) { uint8_t val = 0; int i; for (i = 0; prefix_length > 0; prefix_length -= 8, i++) val ^= dst->s6_addr[i]; return (val); } /* * Given a prefix length, fill in the struct in6_addr representing an IPv6 * netmask. */ static void rtmask_to_bits(uint_t prefix_length, struct in6_addr *prefix) { uint_t mask = 0xff; int i; bzero((caddr_t)prefix, sizeof (struct in6_addr)); for (i = 0; prefix_length >= 8; prefix_length -= 8, i++) prefix->s6_addr[i] = 0xff; mask = (mask << (8 - prefix_length)); if (mask != 0) prefix->s6_addr[i] = mask; } void rtcreate_prefix(struct in6_addr *p1, struct in6_addr *dst, int bits) { uchar_t mask; int j; for (j = 0; bits >= 8; bits -= 8, j++) dst->s6_addr[j] = p1->s6_addr[j]; if (bits != 0) { mask = 0xff << (8 - bits); dst->s6_addr[j] = p1->s6_addr[j] & mask; j++; } for (; j < 16; j++) dst->s6_addr[j] = 0; } /* * Lookup dst in the tables for an exact match. */ struct rt_entry * rtlookup(struct in6_addr *dst, int prefix_length) { struct rt_entry *rt; struct rthash *rh; uint_t hash; if (net_hashes[prefix_length] == NULL) return (NULL); hash = rthash(dst, prefix_length); rh = &net_hashes[prefix_length][hash & ROUTEHASHMASK]; for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw) { if (rt->rt_hash != hash) continue; if (IN6_ARE_ADDR_EQUAL(&rt->rt_dst, dst) && rt->rt_prefix_length == prefix_length) return (rt); } return (NULL); } /* * Given an IPv6 prefix (destination and prefix length), a gateway, an * interface name and route flags, send down the requested command returning * the return value and errno (in the case of error) from the write() on the * routing socket. */ static int rtcmd(uchar_t type, struct in6_addr *dst, struct in6_addr *gateway, uint_t prefix_length, char *name, int flags) { int rlen; rta_ifp->sdl_index = if_nametoindex(name); if (rta_ifp->sdl_index == 0) return (-1); rta_dst->sin6_addr = *dst; rta_gateway->sin6_addr = *gateway; rtmask_to_bits(prefix_length, &rta_netmask->sin6_addr); rt_msg->rtm_type = type; rt_msg->rtm_flags = flags; rt_msg->rtm_seq = ++rtmseq; rlen = write(rtsock, rt_msg, RIPNG_RTM_MSGLEN); if (rlen >= 0 && rlen < RIPNG_RTM_MSGLEN) { syslog(LOG_ERR, "rtcmd: write to routing socket got only %d for rlen\n", rlen); } return (rlen); } void rtadd(struct in6_addr *dst, struct in6_addr *gate, int prefix_length, int metric, int tag, boolean_t ifroute, struct interface *ifp) { struct rt_entry *rt; struct rthash *rh; uint_t hash; struct in6_addr pdst; int rlen; if (metric >= HOPCNT_INFINITY) return; if (net_hashes[prefix_length] == NULL) { struct rthash *trh; rh = (struct rthash *) calloc(ROUTEHASHSIZ, sizeof (struct rt_entry)); if (rh == NULL) return; for (trh = rh; trh < &rh[ROUTEHASHSIZ]; trh++) trh->rt_forw = trh->rt_back = (struct rt_entry *)trh; net_hashes[prefix_length] = rh; } rtcreate_prefix(dst, &pdst, prefix_length); hash = rthash(&pdst, prefix_length); rh = &net_hashes[prefix_length][hash & ROUTEHASHMASK]; rt = (struct rt_entry *)malloc(sizeof (*rt)); if (rt == NULL) { /* * In the event of an allocation failure, log the error and * continue since on the next update another attempt will be * made. */ syslog(LOG_ERR, "rtadd: malloc: %m"); return; } rt->rt_hash = hash; rt->rt_dst = pdst; rt->rt_prefix_length = prefix_length; rt->rt_router = *gate; rt->rt_metric = metric; rt->rt_tag = tag; rt->rt_timer = 0; rt->rt_flags = RTF_UP; if (prefix_length == IPV6_ABITS) rt->rt_flags |= RTF_HOST; rt->rt_state = RTS_CHANGED; if (ifroute) { rt->rt_state |= RTS_INTERFACE; if (ifp->int_flags & RIP6_IFF_PRIVATE) rt->rt_state |= RTS_PRIVATE; } else { rt->rt_flags |= RTF_GATEWAY; } rt->rt_ifp = ifp; insque(rt, rh); TRACE_ACTION("ADD", rt); /* * If the RTM_ADD fails because the gateway is unreachable * from this host, discard the entry. This should never * happen. */ if (install && (rt->rt_state & RTS_INTERFACE) == 0) { rlen = rtcmd(RTM_ADD, &rt->rt_dst, &rt->rt_router, prefix_length, ifp->int_name, rt->rt_flags); if (rlen < 0) { if (errno != EEXIST) { syslog(LOG_ERR, "rtadd: RTM_ADD: %m"); log_single(LOG_ERR, rt); } if (errno == ENETUNREACH) { TRACE_ACTION("DELETE", rt); remque(rt); free((char *)rt); } } else if (rlen < RIPNG_RTM_MSGLEN) { log_single(LOG_ERR, rt); } } } /* * Handle the case when the metric changes but the gateway is the same (or the * interface index associated with the gateway changes), or when both gateway * and metric changes, or when only the gateway changes but the existing route * is more than one-half of EXPIRE_TIME in age. Note that routes with metric >= * HOPCNT_INFINITY are not in the kernel. */ void rtchange(struct rt_entry *rt, struct in6_addr *gate, short metric, struct interface *ifp) { boolean_t dokern = _B_FALSE; boolean_t dokerndelete; boolean_t metricchanged = _B_FALSE; int oldmetric; struct rt_entry oldroute; int rlen; if (metric >= HOPCNT_INFINITY) { rtdown(rt); return; } if (!IN6_ARE_ADDR_EQUAL(&rt->rt_router, gate) || rt->rt_ifp != ifp) dokern = _B_TRUE; oldmetric = rt->rt_metric; if (oldmetric >= HOPCNT_INFINITY) dokerndelete = _B_FALSE; else dokerndelete = dokern; if (metric != rt->rt_metric) metricchanged = _B_TRUE; rt->rt_timer = 0; if (dokern || metricchanged) { TRACE_ACTION("CHANGE FROM", rt); if ((rt->rt_state & RTS_INTERFACE) && metric != 0) { rt->rt_state &= ~RTS_INTERFACE; if (rt->rt_ifp != NULL) { syslog(LOG_ERR, "rtchange: changing route from " "interface %s (timed out)", (rt->rt_ifp->int_name != NULL) ? rt->rt_ifp->int_name : "(noname)"); } else { syslog(LOG_ERR, "rtchange: " "changing route no interface for route"); } } if (dokern) { oldroute = *rt; rt->rt_router = *gate; rt->rt_ifp = ifp; } rt->rt_metric = metric; if (!(rt->rt_state & RTS_INTERFACE)) rt->rt_flags |= RTF_GATEWAY; else rt->rt_flags &= ~RTF_GATEWAY; rt->rt_state |= RTS_CHANGED; TRACE_ACTION("CHANGE TO", rt); } if (install && (rt->rt_state & RTS_INTERFACE) == 0) { if (dokerndelete) { rlen = rtcmd(RTM_ADD, &rt->rt_dst, &rt->rt_router, rt->rt_prefix_length, rt->rt_ifp->int_name, rt->rt_flags); if (rlen < 0) { if (errno != EEXIST) { syslog(LOG_ERR, "rtchange: RTM_ADD: %m"); log_change(LOG_ERR, rt, (struct rt_entry *)&oldroute); } } else if (rlen < RIPNG_RTM_MSGLEN) { log_change(LOG_ERR, rt, (struct rt_entry *)&oldroute); } rlen = rtcmd(RTM_DELETE, &oldroute.rt_dst, &oldroute.rt_router, oldroute.rt_prefix_length, oldroute.rt_ifp->int_name, oldroute.rt_flags); if (rlen < 0) { syslog(LOG_ERR, "rtchange: RTM_DELETE: %m"); log_change(LOG_ERR, rt, (struct rt_entry *)&oldroute); } else if (rlen < RIPNG_RTM_MSGLEN) { log_change(LOG_ERR, rt, (struct rt_entry *)&oldroute); } } else if (dokern || oldmetric >= HOPCNT_INFINITY) { rlen = rtcmd(RTM_ADD, &rt->rt_dst, &rt->rt_router, rt->rt_prefix_length, ifp->int_name, rt->rt_flags); if (rlen < 0 && errno != EEXIST) { syslog(LOG_ERR, "rtchange: RTM_ADD: %m"); log_change(LOG_ERR, rt, (struct rt_entry *)&oldroute); } else if (rlen < RIPNG_RTM_MSGLEN) { log_change(LOG_ERR, rt, (struct rt_entry *)&oldroute); } } } } void rtdown(struct rt_entry *rt) { int rlen; if (rt->rt_metric != HOPCNT_INFINITY) { TRACE_ACTION("DELETE", rt); if (install && (rt->rt_state & RTS_INTERFACE) == 0) { rlen = rtcmd(RTM_DELETE, &rt->rt_dst, &rt->rt_router, rt->rt_prefix_length, rt->rt_ifp->int_name, rt->rt_flags); if (rlen < 0) { syslog(LOG_ERR, "rtdown: RTM_DELETE: %m"); log_single(LOG_ERR, rt); } else if (rlen < RIPNG_RTM_MSGLEN) { log_single(LOG_ERR, rt); } } rt->rt_metric = HOPCNT_INFINITY; rt->rt_state |= RTS_CHANGED; } if (rt->rt_timer < EXPIRE_TIME) rt->rt_timer = EXPIRE_TIME; } void rtdelete(struct rt_entry *rt) { if (rt->rt_state & RTS_INTERFACE) { if (rt->rt_ifp != NULL) { syslog(LOG_ERR, "rtdelete: " "deleting route to interface %s (timed out)", (rt->rt_ifp->int_name != NULL) ? rt->rt_ifp->int_name : "(noname)"); log_single(LOG_ERR, rt); } } rtdown(rt); remque(rt); free((char *)rt); } /* * Mark all the routes heard off a particular interface "down". Unlike the * routes managed by in.routed, all of these routes have an interface associated * with them. */ void rtpurgeif(struct interface *ifp) { struct rthash *rh; struct rt_entry *rt; int i; for (i = IPV6_ABITS; i >= 0; i--) { if (net_hashes[i] == NULL) continue; for (rh = net_hashes[i]; rh < &net_hashes[i][ROUTEHASHSIZ]; rh++) { for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw) { if (rt->rt_ifp == ifp) { rtdown(rt); rt->rt_ifp = NULL; rt->rt_state &= ~RTS_INTERFACE; } } } } } /* * Called when the subnetmask has changed on one or more interfaces. * Re-evaluates all non-interface routes by doing a rtchange so that * routes that were believed to be host routes before the netmask change * can be converted to network routes and vice versa. */ void rtchangeall(void) { struct rthash *rh; struct rt_entry *rt; int i; for (i = IPV6_ABITS; i >= 0; i--) { if (net_hashes[i] == NULL) continue; for (rh = net_hashes[i]; rh < &net_hashes[i][ROUTEHASHSIZ]; rh++) { for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw) { if ((rt->rt_state & RTS_INTERFACE) == 0) { rtchange(rt, &rt->rt_router, rt->rt_metric, rt->rt_ifp); } } } } } static void rtdumpentry(FILE *fp, struct rt_entry *rt) { char buf1[INET6_ADDRSTRLEN]; static struct bits { ulong_t t_bits; char *t_name; } flagbits[] = { /* BEGIN CSTYLED */ { RTF_UP, "UP" }, { RTF_GATEWAY, "GATEWAY" }, { RTF_HOST, "HOST" }, { 0, NULL } /* END CSTYLED */ }, statebits[] = { /* BEGIN CSTYLED */ { RTS_INTERFACE, "INTERFACE" }, { RTS_CHANGED, "CHANGED" }, { RTS_PRIVATE, "PRIVATE" }, { 0, NULL } /* END CSTYLED */ }; struct bits *p; boolean_t first; char c; (void) fprintf(fp, "prefix %s/%d ", inet_ntop(AF_INET6, (void *)&rt->rt_dst, buf1, sizeof (buf1)), rt->rt_prefix_length); (void) fprintf(fp, "via %s metric %d timer %d", inet_ntop(AF_INET6, (void *)&rt->rt_router, buf1, sizeof (buf1)), rt->rt_metric, rt->rt_timer); if (rt->rt_ifp != NULL) { (void) fprintf(fp, " if %s", (rt->rt_ifp->int_name != NULL) ? rt->rt_ifp->int_name : "(noname)"); } (void) fprintf(fp, " state"); c = ' '; for (first = _B_TRUE, p = statebits; p->t_bits > 0; p++) { if ((rt->rt_state & p->t_bits) == 0) continue; (void) fprintf(fp, "%c%s", c, p->t_name); if (first) { c = '|'; first = _B_FALSE; } } if (first) (void) fprintf(fp, " 0"); if (rt->rt_flags & (RTF_UP | RTF_GATEWAY)) { c = ' '; for (first = _B_TRUE, p = flagbits; p->t_bits > 0; p++) { if ((rt->rt_flags & p->t_bits) == 0) continue; (void) fprintf(fp, "%c%s", c, p->t_name); if (first) { c = '|'; first = _B_FALSE; } } } (void) putc('\n', fp); (void) fflush(fp); } static void rtdump2(FILE *fp) { struct rthash *rh; struct rt_entry *rt; int i; for (i = IPV6_ABITS; i >= 0; i--) { if (net_hashes[i] == NULL) continue; for (rh = net_hashes[i]; rh < &net_hashes[i][ROUTEHASHSIZ]; rh++) { for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw) { rtdumpentry(fp, rt); } } } } void rtdump(void) { if (ftrace != NULL) rtdump2(ftrace); else rtdump2(stderr); } /* * Create a routing socket for sending RTM_ADD and RTM_DELETE messages and * initialize the routing socket message header and as much of the sockaddrs * as possible. */ void setup_rtsock(void) { char *cp; int off = 0; rtsock = socket(PF_ROUTE, SOCK_RAW, AF_INET6); if (rtsock < 0) { syslog(LOG_ERR, "setup_rtsock: socket: %m"); exit(EXIT_FAILURE); } /* We don't want to listen to our own messages */ if (setsockopt(rtsock, SOL_SOCKET, SO_USELOOPBACK, (char *)&off, sizeof (off)) < 0) { syslog(LOG_ERR, "setup_rtsock: setsockopt: SO_USELOOPBACK: %m"); exit(EXIT_FAILURE); } /* * Allocate storage for the routing socket message. */ rt_msg = (struct rt_msghdr *)malloc(RIPNG_RTM_MSGLEN); if (rt_msg == NULL) { syslog(LOG_ERR, "setup_rtsock: malloc: %m"); exit(EXIT_FAILURE); } /* * Initialize the routing socket message by zero-filling it and then * setting the fields where are constant through the lifetime of the * process. */ bzero(rt_msg, RIPNG_RTM_MSGLEN); rt_msg->rtm_msglen = RIPNG_RTM_MSGLEN; rt_msg->rtm_version = RTM_VERSION; rt_msg->rtm_addrs = RTA_DST | RTA_GATEWAY | RTA_NETMASK | RTA_IFP; rt_msg->rtm_pid = getpid(); if (rt_msg->rtm_pid < 0) { syslog(LOG_ERR, "setup_rtsock: getpid: %m"); exit(EXIT_FAILURE); } /* * Initialize the constant portion of the RTA_DST sockaddr. */ cp = (char *)rt_msg + sizeof (struct rt_msghdr); rta_dst = (struct sockaddr_in6 *)cp; rta_dst->sin6_family = AF_INET6; /* * Initialize the constant portion of the RTA_GATEWAY sockaddr. */ cp += sizeof (struct sockaddr_in6); rta_gateway = (struct sockaddr_in6 *)cp; rta_gateway->sin6_family = AF_INET6; /* * Initialize the constant portion of the RTA_NETMASK sockaddr. */ cp += sizeof (struct sockaddr_in6); rta_netmask = (struct sockaddr_in6 *)cp; rta_netmask->sin6_family = AF_INET6; /* * Initialize the constant portion of the RTA_IFP sockaddr. */ cp += sizeof (struct sockaddr_in6); rta_ifp = (struct sockaddr_dl *)cp; rta_ifp->sdl_family = AF_LINK; } /* * 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 2003 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Routing Table Management Daemon */ #include "defs.h" int supplyinterval; /* current supply interval */ /* * Timer routine. Performs routing information supply * duties and manages timers on routing table entries. * Management of the RTS_CHANGED bit assumes that we multicast * each time called. */ void timer(void) { struct rthash *rh; struct rt_entry *rt; boolean_t timetomulticast = _B_FALSE; int i; static int iftime; /* interface timer */ static int mtime; /* periodic mcast supply timer */ static int alarmtime = 0; /* time elapsed since last call */ int mintime; /* tracks when next timer will expire */ /* * On the initial call to timer(), the various times that are kept track * of need to be initialized. After initializing everything, "remember" * (via a static) how long until the next timer expires. */ if (alarmtime == 0) { supplyinterval = GET_RANDOM(MIN_SUPPLY_TIME, MAX_SUPPLY_TIME); iftime = 0; mtime = supplyinterval; alarmtime = supplyinterval; (void) alarm(alarmtime); return; } /* * Initialize mintime to a suitable "large" value and then compare it to * other times in the future to determine which event will occur next. */ mintime = INT_MAX; (void) sighold(SIGHUP); (void) sighold(SIGUSR1); (void) sighold(SIGUSR2); iftime += alarmtime; if (iftime >= CHECK_INTERVAL) { initifs(); iftime = 0; } mintime = min(mintime, CHECK_INTERVAL - iftime); mtime += alarmtime; if (mtime >= supplyinterval) { if (supplier) timetomulticast = _B_TRUE; mtime = 0; supplyinterval = GET_RANDOM(MIN_SUPPLY_TIME, MAX_SUPPLY_TIME); } mintime = min(mintime, supplyinterval - mtime); for (i = IPV6_ABITS; i >= 0; i--) { if (net_hashes[i] == NULL) continue; for (rh = net_hashes[i]; rh < &net_hashes[i][ROUTEHASHSIZ]; rh++) { for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw) { /* * We don't advance time on a routing entry for * an interface because we catch * interfaces going up and down in initifs. */ rt->rt_state &= ~RTS_CHANGED; if ((rt->rt_state & RTS_INTERFACE) != 0) continue; rt->rt_timer += alarmtime; if (rt->rt_timer >= GARBAGE_TIME) { rt = rt->rt_back; rtdelete(rt->rt_forw); continue; } if (rt->rt_timer >= EXPIRE_TIME) { rtdown(rt); mintime = min(mintime, GARBAGE_TIME - rt->rt_timer); } else { mintime = min(mintime, EXPIRE_TIME - rt->rt_timer); } } } } if (timetomulticast) { supplyall(&allrouters, 0, (struct interface *)NULL, _B_TRUE); (void) gettimeofday(&now, (struct timezone *)NULL); lastmcast = now; lastfullupdate = now; needupdate = _B_FALSE; /* cancel any pending dynamic update */ nextmcast.tv_sec = 0; } (void) sigrelse(SIGUSR2); (void) sigrelse(SIGUSR1); (void) sigrelse(SIGHUP); /* * "Remember" (via a static) how long until the next timer expires. */ alarmtime = mintime; (void) alarm(alarmtime); } /* * On SIGTERM, let everyone know we're going away. */ void term(void) { struct rthash *rh; struct rt_entry *rt; int i; if (!supplier) exit(EXIT_SUCCESS); for (i = IPV6_ABITS; i >= 0; i--) { if (net_hashes[i] == NULL) continue; for (rh = net_hashes[i]; rh < &net_hashes[i][ROUTEHASHSIZ]; rh++) { for (rt = rh->rt_forw; rt != (struct rt_entry *)rh; rt = rt->rt_forw) { rt->rt_metric = HOPCNT_INFINITY; } } } supplyall(&allrouters, 0, (struct interface *)NULL, _B_TRUE); (void) unlink(PATH_PID); exit(EXIT_SUCCESS); } /* * 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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Routing Table Management Daemon */ #include "defs.h" #define NRECORDS 50 /* size of circular trace buffer */ boolean_t tracepackets; /* watch packets as they go by */ int tracing; /* bitmask: */ FILE *ftrace; /* output trace file */ static int iftraceinit(struct interface *ifp, struct ifdebug *ifd); static void dumpif(FILE *fp, struct interface *ifp); static void dumptrace(FILE *fp, char *dir, struct ifdebug *ifd); void traceinit(struct interface *ifp) { if (iftraceinit(ifp, &ifp->int_input) && iftraceinit(ifp, &ifp->int_output)) return; tracing = 0; (void) fprintf(stderr, "traceinit: can't init %s\n", (ifp->int_name != NULL) ? ifp->int_name : "(noname)"); } static int iftraceinit(struct interface *ifp, struct ifdebug *ifd) { struct iftrace *t; ifd->ifd_records = (struct iftrace *) malloc((size_t)NRECORDS * sizeof (struct iftrace)); if (ifd->ifd_records == NULL) return (0); ifd->ifd_front = ifd->ifd_records; ifd->ifd_count = 0; for (t = ifd->ifd_records; t < ifd->ifd_records + NRECORDS; t++) { t->ift_size = 0; t->ift_packet = NULL; } ifd->ifd_if = ifp; return (1); } void traceon(char *file) { struct stat stbuf; if (ftrace != NULL) return; if (stat(file, &stbuf) >= 0 && (stbuf.st_mode & S_IFMT) != S_IFREG) return; ftrace = fopen(file, "a"); if (ftrace == NULL) return; (void) dup2(fileno(ftrace), 1); (void) dup2(fileno(ftrace), 2); } void traceonfp(FILE *fp) { if (ftrace != NULL) return; ftrace = fp; if (ftrace == NULL) return; (void) dup2(fileno(ftrace), 1); (void) dup2(fileno(ftrace), 2); } void trace(struct ifdebug *ifd, struct sockaddr_in6 *who, char *p, int len, int m) { struct iftrace *t; if (ifd->ifd_records == 0) return; t = ifd->ifd_front++; if (ifd->ifd_front >= ifd->ifd_records + NRECORDS) ifd->ifd_front = ifd->ifd_records; if (ifd->ifd_count < NRECORDS) ifd->ifd_count++; if (t->ift_size > 0 && t->ift_size < len && t->ift_packet != NULL) { free(t->ift_packet); t->ift_packet = NULL; } (void) time(&t->ift_stamp); t->ift_who = *who; if (len > 0 && t->ift_packet == NULL) { t->ift_packet = (char *)malloc((size_t)len); if (t->ift_packet == NULL) len = 0; } if (len > 0) bcopy(p, t->ift_packet, len); t->ift_size = len; t->ift_metric = m; } void traceaction(FILE *fp, char *action, struct rt_entry *rt) { static struct bits { ulong_t t_bits; char *t_name; } flagbits[] = { /* BEGIN CSTYLED */ { RTF_UP, "UP" }, { RTF_GATEWAY, "GATEWAY" }, { RTF_HOST, "HOST" }, { 0, NULL } /* END CSTYLED */ }, statebits[] = { /* BEGIN CSTYLED */ { RTS_INTERFACE, "INTERFACE" }, { RTS_CHANGED, "CHANGED" }, { RTS_PRIVATE, "PRIVATE" }, { 0, NULL } /* END CSTYLED */ }; struct bits *p; boolean_t first; char c; time_t t; if (fp == NULL) return; (void) time(&t); (void) fprintf(fp, "%.15s %s ", ctime(&t) + 4, action); if (rt != NULL) { char buf1[INET6_ADDRSTRLEN]; (void) fprintf(fp, "prefix %s/%d ", inet_ntop(AF_INET6, (void *)&rt->rt_dst, buf1, sizeof (buf1)), rt->rt_prefix_length); (void) fprintf(fp, "via %s metric %d", inet_ntop(AF_INET6, (void *)&rt->rt_router, buf1, sizeof (buf1)), rt->rt_metric); if (rt->rt_ifp != NULL) { (void) fprintf(fp, " if %s", (rt->rt_ifp->int_name != NULL) ? rt->rt_ifp->int_name : "(noname)"); } (void) fprintf(fp, " state"); c = ' '; for (first = _B_TRUE, p = statebits; p->t_bits > 0; p++) { if ((rt->rt_state & p->t_bits) == 0) continue; (void) fprintf(fp, "%c%s", c, p->t_name); if (first) { c = '|'; first = _B_FALSE; } } if (first) (void) fprintf(fp, " 0"); if (rt->rt_flags & (RTF_UP | RTF_GATEWAY)) { c = ' '; for (first = _B_TRUE, p = flagbits; p->t_bits > 0; p++) { if ((rt->rt_flags & p->t_bits) == 0) continue; (void) fprintf(fp, "%c%s", c, p->t_name); if (first) { c = '|'; first = _B_FALSE; } } } } (void) putc('\n', fp); if (!tracepackets && rt != NULL && rt->rt_ifp != NULL) dumpif(fp, rt->rt_ifp); (void) fflush(fp); } static void dumpif(FILE *fp, struct interface *ifp) { if (ifp->int_input.ifd_count != 0 || ifp->int_output.ifd_count != 0) { (void) fprintf(fp, "*** Packet history for interface %s ***\n", (ifp->int_name != NULL) ? ifp->int_name : "(noname)"); dumptrace(fp, "to", &ifp->int_output); dumptrace(fp, "from", &ifp->int_input); (void) fprintf(fp, "*** end packet history ***\n"); } (void) fflush(fp); } static void dumptrace(FILE *fp, char *dir, struct ifdebug *ifd) { struct iftrace *t; char *cp = (strcmp(dir, "to") != 0) ? "Output" : "Input"; if (ifd->ifd_front == ifd->ifd_records && ifd->ifd_front->ift_size == 0) { (void) fprintf(fp, "%s: no packets.\n", cp); (void) fflush(fp); return; } (void) fprintf(fp, "%s trace:\n", cp); t = ifd->ifd_front - ifd->ifd_count; if (t < ifd->ifd_records) t += NRECORDS; for (; ifd->ifd_count; ifd->ifd_count--, t++) { if (t >= ifd->ifd_records + NRECORDS) t = ifd->ifd_records; if (t->ift_size == 0) continue; (void) fprintf(fp, "%.24s: metric=%d\n", ctime(&t->ift_stamp), t->ift_metric); dumppacket(fp, dir, (struct sockaddr_in6 *)&t->ift_who, t->ift_packet, t->ift_size); } } /*ARGSUSED*/ void dumppacket(FILE *fp, char *dir, struct sockaddr_in6 *who, char *cp, int size) { /* XXX Output contents of the RIP packet */ } /* * 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 1999 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* Copyright (c) 1983, 1984, 1985, 1986, 1987, 1988, 1989 AT&T */ /* All Rights Reserved */ /* * Portions of this source code were derived from Berkeley 4.3 BSD * under license from the Regents of the University of California. */ /* * Routing table management daemon. */ /* * Trace record format. */ struct iftrace { time_t ift_stamp; /* time stamp */ struct sockaddr_in6 ift_who; /* from/to */ char *ift_packet; /* pointer to packet */ int ift_size; /* size of packet */ int ift_metric; /* metric on associated metric */ }; /* * Per interface packet tracing buffers. An incoming and * outgoing circular buffer of packets is maintained, per * interface, for debugging. Buffers are dumped whenever * an interface is marked down. */ struct ifdebug { struct iftrace *ifd_records; /* array of trace records */ struct iftrace *ifd_front; /* next empty trace record */ int ifd_count; /* number of unprinted records */ struct interface *ifd_if; /* for locating stuff */ }; /* * Packet tracing stuff. */ extern FILE *ftrace; extern boolean_t tracepackets; extern int tracing; #define ACTION_BIT 0x0001 #define INPUT_BIT 0x0002 #define OUTPUT_BIT 0x0004 #define TRACE_ACTION(action, route) { \ if (tracing & ACTION_BIT) \ traceaction(ftrace, (action), (route)); \ } #define TRACE_INPUT(ifp, src, size) { \ if ((tracing & INPUT_BIT) && ((ifp) != NULL)) { \ trace(&(ifp)->int_input, (src), packet, (size), \ (ifp)->int_metric); \ } \ if (tracepackets) { \ dumppacket(stdout, "from", (struct sockaddr_in6 *)(src), \ packet, (size)); \ } \ } #define TRACE_OUTPUT(ifp, dst, size) { \ if ((tracing & OUTPUT_BIT) && ((ifp) != NULL)) { \ trace(&(ifp)->int_output, (dst), packet, (size), \ (ifp)->int_metric); \ } \ if (tracepackets) { \ dumppacket(stdout, "to", (struct sockaddr_in6 *)(dst), \ packet, (size)); \ } \ } extern void dumppacket(FILE *, char *, struct sockaddr_in6 *, char *, int); extern void trace(struct ifdebug *, struct sockaddr_in6 *, char *, int, int); extern void traceaction(FILE *, char *, struct rt_entry *); extern void traceinit(struct interface *); extern void traceon(char *); extern void traceonfp(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. # # Copyright (c) 2018, Joyent, Inc. PROG = inetd MANIFEST= inetd.xml inetd-upgrade.xml SVCMETHOD= inetd-upgrade OBJS = inetd.o tlx.o config.o util.o contracts.o repval.o wait.o env.o include ../../../Makefile.cmd include ../../Makefile.cmd-inet include ../../../Makefile.ctf ROOTMANIFESTDIR= $(ROOTSVCNETWORK) CPPFLAGS += -D_FILE_OFFSET_BITS=64 -I$(CMDINETCOMMONDIR) -D_REENTRANT $(RELEASE_BUILD)CPPFLAGS += -DNDEBUG CERRWARN += -Wno-switch SMOFF += kmalloc_wrong_size LDLIBS += -lsocket -lnsl -lrestart -lscf -lcontract -linetutil \ -lwrap -linetsvc -luutil -lumem -lbsm CLOBBERFILES += $(SVCMETHOD) .PARALLEL: $(OBJS) .WAIT: $(PROG) .KEEP_STATE: all: $(PROG) $(SVCMETHOD) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST) $(ROOTSVCMETHOD) -$(RM) $(ROOTUSRSBINPROG) -$(SYMLINK) ../lib/inet/${PROG} $(ROOTUSRSBINPROG) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) 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. */ /* * Routines used by inetd to read inetd's configuration from the repository, * to validate it and setup inetd's data structures appropriately based on * in. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* method timeout used if one isn't explicitly specified */ #define DEFAULT_METHOD_TIMEOUT 10 /* supported method properties and their attributes */ static inetd_prop_t method_props[] = { {PR_EXEC_NAME, "", INET_TYPE_STRING, B_FALSE, IVE_UNSET, 0, B_FALSE}, {PR_ARG0_NAME, "", INET_TYPE_STRING, B_TRUE, IVE_UNSET, 0, B_FALSE}, {SCF_PROPERTY_TIMEOUT, "", INET_TYPE_COUNT, B_TRUE, IVE_UNSET, 0, B_FALSE}, {NULL}, }; /* enumeration of method properties; used to index into method_props[] */ typedef enum { MP_EXEC, MP_ARG0, MP_TIMEOUT } method_prop_t; /* handle used for repository access in read_prop() */ static scf_handle_t *rep_handle = NULL; /* pool used to create proto_info_t lists (generic proto info structure) */ static uu_list_pool_t *proto_info_pool = NULL; static void destroy_method_props(inetd_prop_t *); static int proto_info_compare(const void *, const void *, void *); int config_init(void) { if ((rep_handle = scf_handle_create(SCF_VERSION)) == NULL) { error_msg("%s: %s", gettext("Failed to create repository handle"), scf_strerror(scf_error())); return (-1); } else if (make_handle_bound(rep_handle) == -1) { /* let config_fini clean-up */ return (-1); } if ((proto_info_pool = uu_list_pool_create("proto_info_pool", sizeof (proto_info_t), offsetof(proto_info_t, link), proto_info_compare, UU_LIST_POOL_DEBUG)) == NULL) { error_msg(gettext("Failed to create uu list pool: %s"), uu_strerror(uu_error())); return (-1); } return (0); } void config_fini(void) { if (rep_handle == NULL) return; if (proto_info_pool != NULL) { uu_list_pool_destroy(proto_info_pool); proto_info_pool = NULL; } (void) scf_handle_unbind(rep_handle); scf_handle_destroy(rep_handle); rep_handle = NULL; } static void destroy_method_info(method_info_t *mi) { if (mi == NULL) return; if (mi->wordexp_arg0_backup != NULL) { /* * Return the wordexp structure back to its original * state so it can be consumed by wordfree. */ free(mi->exec_args_we.we_wordv[0]); mi->exec_args_we.we_wordv[0] = (char *)mi->wordexp_arg0_backup; } free(mi->exec_path); wordfree(&mi->exec_args_we); free(mi); } /* * Transforms the properties read from the repository for a method into a * method_info_t and returns a pointer to it. If expansion of the exec * property fails, due to an invalid string or memory allocation failure, * NULL is returned and exec_invalid is set appropriately to indicate whether * it was a memory allocation failure or an invalid exec string. */ static method_info_t * create_method_info(const inetd_prop_t *mprops, boolean_t *exec_invalid) { method_info_t *ret; int i; if ((ret = calloc(1, sizeof (method_info_t))) == NULL) goto alloc_fail; /* Expand the exec string. */ if ((i = wordexp(get_prop_value_string(mprops, PR_EXEC_NAME), &ret->exec_args_we, WRDE_NOCMD|WRDE_UNDEF)) != 0) { if (i == WRDE_NOSPACE) goto alloc_fail; *exec_invalid = B_TRUE; free(ret); return (NULL); } if ((ret->exec_path = strdup(ret->exec_args_we.we_wordv[0])) == NULL) goto alloc_fail; if (mprops[MP_ARG0].ip_error == IVE_VALID) { /* arg0 is set */ /* * Keep a copy of arg0 of the wordexp structure so that * wordfree() gets passed what wordexp() originally returned, * as documented as required in the man page. */ ret->wordexp_arg0_backup = ret->exec_args_we.we_wordv[0]; if ((ret->exec_args_we.we_wordv[0] = strdup(get_prop_value_string(mprops, PR_ARG0_NAME))) == NULL) goto alloc_fail; } if (mprops[MP_TIMEOUT].ip_error == IVE_VALID) { ret->timeout = get_prop_value_count(mprops, SCF_PROPERTY_TIMEOUT); } else { ret->timeout = DEFAULT_METHOD_TIMEOUT; } /* exec_invalid not set on success */ return (ret); alloc_fail: error_msg(strerror(errno)); destroy_method_info(ret); *exec_invalid = B_FALSE; return (NULL); } /* * Returns B_TRUE if the contents of the 2 method_info_t structures are * equivalent, else B_FALSE. */ boolean_t method_info_equal(const method_info_t *mi, const method_info_t *mi2) { int i; if ((mi == NULL) && (mi2 == NULL)) { return (B_TRUE); } else if (((mi == NULL) || (mi2 == NULL)) || (mi->exec_args_we.we_wordc != mi2->exec_args_we.we_wordc) || (strcmp(mi->exec_path, mi2->exec_path) != 0)) { return (B_FALSE); } for (i = 0; i < mi->exec_args_we.we_wordc; i++) { if (strcmp(mi->exec_args_we.we_wordv[i], mi2->exec_args_we.we_wordv[i]) != 0) { return (B_FALSE); } } return (B_TRUE); } /* * Checks if the contents of the 2 socket_info_t structures are equivalent. * If 'isrpc' is false, the address components of the two structures are * compared for equality as part of this. If the two structures are * equivalent B_TRUE is returned, else B_FALSE. */ boolean_t socket_info_equal(const socket_info_t *si, const socket_info_t *si2, boolean_t isrpc) { return ((isrpc || (memcmp(&si->local_addr, &si2->local_addr, sizeof (si->local_addr)) == 0)) && (si->type == si2->type)); } /* * proto_info_t comparison function. Returns 0 on match, else -1, as required * by uu_list_find(). */ static int proto_info_compare(const void *lv, const void *rv, void *istlx) { proto_info_t *pi = (proto_info_t *)lv; proto_info_t *pi2 = (proto_info_t *)rv; /* check their RPC configuration matches */ if (pi->ri != NULL) { if ((pi2->ri == NULL) || !rpc_info_equal(pi->ri, pi2->ri)) return (-1); } else if (pi2->ri != NULL) { return (-1); } if (pi->v6only != pi2->v6only) return (-1); if (*(boolean_t *)istlx) { if (tlx_info_equal((tlx_info_t *)lv, (tlx_info_t *)rv, pi->ri != NULL)) return (0); } else { if (socket_info_equal((socket_info_t *)lv, (socket_info_t *)rv, pi->ri != NULL)) return (0); } return (-1); } /* * Returns B_TRUE if the bind configuration of the two instance_cfg_t * structures are equivalent, else B_FALSE. */ boolean_t bind_config_equal(const basic_cfg_t *c1, const basic_cfg_t *c2) { proto_info_t *pi; if ((c1->iswait != c2->iswait) || (c1->istlx != c2->istlx)) return (B_FALSE); if (uu_list_numnodes(c1->proto_list) != uu_list_numnodes(c2->proto_list)) return (B_FALSE); /* * For each element in the first configuration's socket/tlx list, * check there's a matching one in the other list. */ for (pi = uu_list_first(c1->proto_list); pi != NULL; pi = uu_list_next(c1->proto_list, pi)) { uu_list_index_t idx; if (uu_list_find(c2->proto_list, pi, (void *)&c1->istlx, &idx) == NULL) return (B_FALSE); } return (B_TRUE); } /* * Write the default values contained in 'bprops', read by * read_instance_props(), into 'cfg'. * Returns -1 if memory allocation fails, else 0. */ static int populate_defaults(inetd_prop_t *bprops, basic_cfg_t *cfg) { cfg->do_tcp_wrappers = get_prop_value_boolean(bprops, PR_DO_TCP_WRAPPERS_NAME); cfg->do_tcp_trace = get_prop_value_boolean(bprops, PR_DO_TCP_TRACE_NAME); cfg->do_tcp_keepalive = get_prop_value_boolean(bprops, PR_DO_TCP_KEEPALIVE_NAME); cfg->inherit_env = get_prop_value_boolean(bprops, PR_INHERIT_ENV_NAME); cfg->wait_fail_cnt = get_prop_value_int(bprops, PR_MAX_FAIL_RATE_CNT_NAME); cfg->wait_fail_interval = get_prop_value_int(bprops, PR_MAX_FAIL_RATE_INTVL_NAME); cfg->max_copies = get_prop_value_int(bprops, PR_MAX_COPIES_NAME); cfg->conn_rate_offline = get_prop_value_int(bprops, PR_CON_RATE_OFFLINE_NAME); cfg->conn_rate_max = get_prop_value_int(bprops, PR_CON_RATE_MAX_NAME); cfg->bind_fail_interval = get_prop_value_int(bprops, PR_BIND_FAIL_INTVL_NAME); cfg->bind_fail_max = get_prop_value_int(bprops, PR_BIND_FAIL_MAX_NAME); cfg->conn_backlog = get_prop_value_int(bprops, PR_CONNECTION_BACKLOG_NAME); if ((cfg->bind_addr = strdup(get_prop_value_string(bprops, PR_BIND_ADDR_NAME))) == NULL) { error_msg(strerror(errno)); return (-1); } return (0); } void destroy_method_infos(method_info_t **mis) { int i; for (i = 0; i < NUM_METHODS; i++) { destroy_method_info(mis[i]); mis[i] = NULL; } } /* * For each method, if it was specifed convert its entry in 'mprops', * into an entry in 'mis'. Returns -1 if memory allocation fails or one of the * exec strings was invalid, else 0. */ static int create_method_infos(const char *fmri, inetd_prop_t **mprops, method_info_t **mis) { int i; for (i = 0; i < NUM_METHODS; i++) { /* * Only create a method info structure if the method properties * contain an exec string, which we take to mean the method * is specified. */ if (mprops[i][MP_EXEC].ip_error == IVE_VALID) { boolean_t exec_invalid; if ((mis[i] = create_method_info(mprops[i], &exec_invalid)) == NULL) { if (exec_invalid) { error_msg(gettext("Property %s for " "method %s of instance %s is " "invalid"), PR_EXEC_NAME, methods[i].name, fmri); } return (-1); } } } return (0); } /* * Try and read each of the method properties for the method 'method' of * instance 'inst', and return a table containing all method properties. If an * error occurs, NULL is returned, with 'err' set to indicate the cause. * Otherwise, a pointer to an inetd_prop_t table is returned containing all * the method properties, and each of the properties is flagged according to * whether it was present or not, and if it was present its value is set in * the property's entry in the table. */ static inetd_prop_t * read_method_props(const char *inst, instance_method_t method, scf_error_t *err) { inetd_prop_t *ret; int i; if ((ret = calloc(1, sizeof (method_props))) == NULL) { *err = SCF_ERROR_NO_MEMORY; return (NULL); } (void) memcpy(ret, method_props, sizeof (method_props)); for (i = 0; ret[i].ip_name != NULL; i++) { *err = read_prop(rep_handle, &ret[i], i, inst, methods[method].name); if ((*err != 0) && (*err != SCF_ERROR_NOT_FOUND)) { destroy_method_props(ret); return (NULL); } } return (ret); } static void destroy_method_props(inetd_prop_t *mprop) { int i; if (mprop == NULL) return; for (i = 0; mprop[i].ip_name != NULL; i++) { if (mprop[i].ip_type == INET_TYPE_STRING && mprop[i].ip_error == IVE_VALID) free(mprop[i].ip_value.iv_string); } free(mprop); } /* * Destroy the basic and method properties returned by read_inst_props(). */ static void destroy_inst_props(inetd_prop_t *bprops, inetd_prop_t **mprops) { int i; free_instance_props(bprops); for (i = 0; i < NUM_METHODS; i++) destroy_method_props(mprops[i]); } /* * Read all the basic and method properties for instance 'inst', as inetd_prop_t * tables, into the spaces referenced by 'bprops' and 'mprops' respectively. * Each of the properties in the tables are flagged to indicate if the * property was present or not, and if it was the value is stored within it. * If an error occurs at any time -1 is returned and 'err' is set to * indicate the reason, else 0 is returned. */ static int read_inst_props(const char *fmri, inetd_prop_t **bprops, inetd_prop_t **mprops, scf_error_t *err) { size_t nprops; int i; if ((*bprops = read_instance_props(rep_handle, (char *)fmri, &nprops, err)) == NULL) return (-1); for (i = 0; i < NUM_METHODS; i++) { if ((mprops[i] = read_method_props(fmri, (instance_method_t)i, err)) == NULL) { for (i--; i >= 0; i--) destroy_method_props(mprops[i]); free_instance_props(*bprops); return (-1); } } return (0); } /* * Returns B_TRUE if all required properties were read from the repository * (whether taken from the defaults or directly from the instance), they * all had valid values, all the required methods were present, and they * each had the required properties with valid values. Else, returns B_FALSE. * If the function returns B_TRUE, the storage referenced by 'cfg' is set * to point at an allocated instance_cfg_t initialized based on the basic * properties (not method or defaults). */ static boolean_t valid_inst_props(const char *fmri, inetd_prop_t *bprops, inetd_prop_t **mprops, basic_cfg_t **cfg) { boolean_t valid; size_t num_bprops; int i; valid = valid_props(bprops, fmri, cfg, proto_info_pool, conn_ind_pool); /* * Double check we've got all necessary properties (valid_props() * doesn't enforce the presence of defaults), and output error messages * for each invalid/ missing property. */ (void) get_prop_table(&num_bprops); for (i = 0; bprops[i].ip_name != NULL; i++) { switch (bprops[i].ip_error) { case IVE_UNSET: if (!bprops[i].ip_default) continue; if ((i == PT_ARG0_INDEX) || (i == PT_EXEC_INDEX)) continue; /* FALLTHROUGH */ case IVE_INVALID: error_msg(gettext("Property '%s' of instance " "%s is missing, inconsistent or invalid"), bprops[i].ip_name, fmri); valid = B_FALSE; } } for (i = 0; i < NUM_METHODS; i++) { int j; /* check if any properties are set */ for (j = 0; mprops[i][j].ip_name != NULL; j++) { if (mprops[i][j].ip_error != IVE_UNSET) break; } if (mprops[i][j].ip_name == NULL) { /* an unspecified method */ if ((instance_method_t)i == IM_START) { error_msg(gettext( "Unspecified %s method for instance %s"), START_METHOD_NAME, fmri); valid = B_FALSE; } } else if (mprops[i][MP_EXEC].ip_error == IVE_UNSET) { error_msg(gettext("Missing %s property from method %s " "of instance %s"), PR_EXEC_NAME, methods[(instance_method_t)i].name, fmri); valid = B_FALSE; } } if (!valid) { destroy_basic_cfg(*cfg); *cfg = NULL; } return (valid); } void destroy_instance_cfg(instance_cfg_t *cfg) { if (cfg != NULL) { destroy_basic_cfg(cfg->basic); destroy_method_infos(cfg->methods); free(cfg); } } /* * Returns an allocated instance_cfg_t representation of an instance's * configuration read from the repository. If the configuration is invalid, a * repository error occurred, or a memory allocation occurred returns NULL, * else returns a pointer to the allocated instance_cfg_t. */ instance_cfg_t * read_instance_cfg(const char *fmri) { uint_t retries; inetd_prop_t *bprops; inetd_prop_t *mprops[NUM_METHODS]; instance_cfg_t *ret = NULL; scf_error_t err; if ((ret = calloc(1, sizeof (instance_cfg_t))) == NULL) return (NULL); for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) { err = scf_error(); goto read_error; } if (read_inst_props(fmri, &bprops, mprops, &err) == 0) break; if (err != SCF_ERROR_CONNECTION_BROKEN) goto read_error; (void) scf_handle_unbind(rep_handle); } if (retries > REP_OP_RETRIES) goto read_error; /* * Switch off validation of the start method's exec string, since * during boot the filesystem it resides on may not have been * mounted yet, which would result in a false validation failure. * We'll catch any real errors when the start method is first run * in passes_basic_exec_checks(). */ bprops[PT_EXEC_INDEX].ip_error = IVE_UNSET; if ((!valid_inst_props(fmri, bprops, mprops, &ret->basic)) || (populate_defaults(bprops, ret->basic) != 0) || (create_method_infos(fmri, mprops, ret->methods) != 0)) { destroy_instance_cfg(ret); ret = NULL; } destroy_inst_props(bprops, mprops); return (ret); read_error: error_msg(gettext( "Failed to read the configuration of instance %s: %s"), fmri, scf_strerror(err)); free(ret); return (NULL); } /* * Returns a pointer to an allocated method context for the specified method * of the specified instance if it could retrieve it. Else, if there were * errors retrieving it, NULL is returned and the pointer referenced by * 'errstr' is set to point at an appropriate error string. */ struct method_context * read_method_context(const char *inst_fmri, const char *method, const char *path) { scf_instance_t *scf_inst = NULL; struct method_context *ret; uint_t retries; mc_error_t *tmperr; char *fail; fail = gettext("Failed to retrieve method context for the %s method of " "instance %s : %s"); for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) goto inst_failure; if (((scf_inst = scf_instance_create(rep_handle)) != NULL) && (scf_handle_decode_fmri(rep_handle, inst_fmri, NULL, NULL, scf_inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == 0)) break; if (scf_error() != SCF_ERROR_CONNECTION_BROKEN) { scf_instance_destroy(scf_inst); goto inst_failure; } (void) scf_instance_destroy(scf_inst); scf_inst = NULL; (void) scf_handle_unbind(rep_handle); } if (retries > REP_OP_RETRIES) goto inst_failure; if ((tmperr = restarter_get_method_context( RESTARTER_METHOD_CONTEXT_VERSION, scf_inst, NULL, method, path, &ret)) != NULL) { ret = NULL; error_msg(fail, method, inst_fmri, tmperr->msg); restarter_mc_error_destroy(tmperr); } scf_instance_destroy(scf_inst); return (ret); inst_failure: /* * We can rely on this string not becoming invalid * since we don't call bind_textdomain_codeset() or * setlocale(3C) after initialization. */ error_msg(fail, method, inst_fmri, gettext("failed to get instance from repository")); return (NULL); } /* * Reads the value of the enabled property from the named property group * of the given instance. * If an error occurs, the SCF error code is returned. The possible errors are: * - SCF_ERROR_INVALID_ARGUMENT: The enabled property is not a boolean. * - SCF_ERROR_NONE: No value exists for the enabled property. * - SCF_ERROR_CONNECTION_BROKEN: Repository connection broken. * - SCF_ERROR_NOT_FOUND: The property wasn't found. * - SCF_ERROR_NO_MEMORY: allocation failure. * Else 0 is returned and 'enabled' set appropriately. */ static scf_error_t read_enable_prop(const char *fmri, boolean_t *enabled, const char *pg) { scf_simple_prop_t *sp; uint8_t *u8p; if ((sp = scf_simple_prop_get(rep_handle, fmri, pg, SCF_PROPERTY_ENABLED)) == NULL) return (scf_error()); if ((u8p = scf_simple_prop_next_boolean(sp)) == NULL) { scf_simple_prop_free(sp); return (scf_error()); } *enabled = (*u8p != 0); scf_simple_prop_free(sp); return (0); } /* * Reads the enabled value for the given instance FMRI. The read value * is based on a merge of the 'standard' enabled property, and the temporary * override one; the merge involves using the latter properties value if * present, else resporting to the formers. If an error occurs -1 is returned, * else 0 is returned and 'enabled' set approriately. */ int read_enable_merged(const char *fmri, boolean_t *enabled) { uint_t retries; for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) goto gen_fail; switch (read_enable_prop(fmri, enabled, SCF_PG_GENERAL_OVR)) { case 0: debug_msg("read %d from override", *enabled); return (0); case SCF_ERROR_CONNECTION_BROKEN: break; case SCF_ERROR_NOT_FOUND: case SCF_ERROR_NONE: case SCF_ERROR_INVALID_ARGUMENT: switch (read_enable_prop(fmri, enabled, SCF_PG_GENERAL)) { case 0: debug_msg("read %d from non_override", *enabled); return (0); case SCF_ERROR_CONNECTION_BROKEN: break; case SCF_ERROR_NOT_FOUND: case SCF_ERROR_NONE: case SCF_ERROR_INVALID_ARGUMENT: error_msg(gettext("Missing %s property/value " "for instance %s"), SCF_PROPERTY_ENABLED, fmri); return (-1); default: goto gen_fail; } break; default: goto gen_fail; } (void) scf_handle_unbind(rep_handle); continue; } gen_fail: error_msg(gettext("Failed to read the %s property of instance %s: %s"), SCF_PROPERTY_ENABLED, fmri, scf_strerror(scf_error())); return (-1); } /* * Refresh the value of debug property under the property group "config" * for network/inetd service. */ void refresh_debug_flag(void) { scf_simple_prop_t *sprop; uint8_t *tmp_bool; if ((sprop = scf_simple_prop_get(rep_handle, INETD_INSTANCE_FMRI, PG_NAME_APPLICATION_CONFIG, PR_NAME_DEBUG_FLAG)) == NULL) { error_msg(gettext("Unable to read %s property from %s property " "group. scf_simple_prop_get() failed: %s"), PR_NAME_DEBUG_FLAG, PG_NAME_APPLICATION_CONFIG, scf_strerror(scf_error())); return; } else if ((tmp_bool = scf_simple_prop_next_boolean(sprop)) == NULL) { error_msg(gettext("Unable to read %s property for %s service. " "scf_simple_prop_next_boolean() failed: %s"), PR_NAME_DEBUG_FLAG, INETD_INSTANCE_FMRI, scf_strerror(scf_error())); } else { debug_enabled = ((*tmp_bool == 0) ? B_FALSE : B_TRUE); } scf_simple_prop_free(sprop); } /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* paths/filenames of contract related files */ #define CONTRACT_ROOT_PATH CTFS_ROOT "/process/" #define CONTRACT_TEMPLATE_PATH CONTRACT_ROOT_PATH "template" static int active_tmpl_fd = -1; /* * Creates and configures the the contract template used for all inetd's * methods. * Returns -1 on error, else the fd of the created template. */ static int create_contract_template(void) { int fd; int err; if ((fd = open(CONTRACT_TEMPLATE_PATH, O_RDWR)) == -1) { error_msg(gettext("Failed to open contract file %s: %s"), CONTRACT_TEMPLATE_PATH, strerror(errno)); return (-1); } /* * Make contract inheritable and make hardware errors fatal. * We also limit the scope of fatal events to the process * group. In order of preference we would have contract-aware * login services or a property indicating which services need * such scoping, but for the time being we'll assume that most * non login-style services run in a single process group. */ if (((err = ct_pr_tmpl_set_param(fd, CT_PR_INHERIT|CT_PR_PGRPONLY)) != 0) || ((err = ct_pr_tmpl_set_fatal(fd, CT_PR_EV_HWERR)) != 0) || ((err = ct_tmpl_set_critical(fd, 0)) != 0) || ((err = ct_tmpl_set_informative(fd, 0)) != 0)) { error_msg(gettext( "Failed to set parameter for contract template: %s"), strerror(err)); (void) close(fd); return (-1); } return (fd); } /* Returns -1 on error, else 0. */ int contract_init(void) { if ((active_tmpl_fd = create_contract_template()) == -1) { error_msg(gettext("Failed to create contract template")); return (-1); } return (0); } void contract_fini(void) { if (active_tmpl_fd != -1) { (void) close(active_tmpl_fd); active_tmpl_fd = -1; } } /* * To be called directly before a service method is forked, this function * results in the method process being in a new contract based on the active * contract template. */ int contract_prefork(const char *fmri, int method) { int err; if ((err = ct_pr_tmpl_set_svc_fmri(active_tmpl_fd, fmri)) != 0) { error_msg(gettext("Failed to set svc_fmri term: %s"), strerror(err)); return (-1); } if ((err = ct_pr_tmpl_set_svc_aux(active_tmpl_fd, methods[method].name)) != 0) { error_msg(gettext("Failed to set svc_aux term: %s"), strerror(err)); return (-1); } if ((err = ct_tmpl_activate(active_tmpl_fd)) != 0) { error_msg(gettext("Failed to activate contract template: %s"), strerror(err)); return (-1); } return (0); } /* * To be called in both processes directly after a service method is forked, * this function results in switching off contract creation for any * forks done by either process, unless contract_prefork() is called beforehand. */ void contract_postfork(void) { int err; if ((err = ct_tmpl_clear(active_tmpl_fd)) != 0) error_msg("Failed to clear active contract template: %s", strerror(err)); } /* * Fetch the latest created contract id into the space referenced by 'cid'. * Returns -1 on error, else 0. */ int get_latest_contract(ctid_t *cid) { if ((errno = contract_latest(cid)) != 0) { error_msg(gettext("Failed to get new contract's id: %s"), strerror(errno)); return (-1); } return (0); } /* Returns -1 on error (with errno set), else fd. */ static int open_contract_ctl_file(ctid_t cid) { return (contract_open(cid, "process", "ctl", O_WRONLY)); } /* * Adopt a contract. Emits an error message and returns -1 on failure, else * 0. */ int adopt_contract(ctid_t ctid, const char *fmri) { int fd; int err; int ret = 0; if ((fd = open_contract_ctl_file(ctid)) == -1) { if (errno == EACCES || errno == ENOENT) { /* * We must not have inherited this contract. That can * happen if we were disabled and restarted. */ debug_msg("Could not adopt contract %ld for %s " "(could not open ctl file: permission denied).\n", ctid, fmri); return (-1); } error_msg(gettext("Could not adopt contract id %ld registered " "with %s (could not open ctl file: %s). Events will be " "ignored."), ctid, fmri, strerror(errno)); return (-1); } if ((err = ct_ctl_adopt(fd)) != 0) { error_msg(gettext("Could not adopt contract id %ld registered " "with %s (%s). Events will be ignored."), ctid, fmri, strerror(err)); ret = -1; } err = close(fd); if (err != 0) error_msg(gettext("Could not close file descriptor %d."), fd); return (ret); } /* Returns -1 on error, else 0. */ int abandon_contract(ctid_t ctid) { int fd; int err; assert(ctid != -1); if ((fd = open_contract_ctl_file(ctid)) == -1) { error_msg(gettext("Failed to abandon contract %d: %s"), ctid, strerror(errno)); return (-1); } if ((err = ct_ctl_abandon(fd)) != 0) { (void) close(fd); error_msg(gettext("Failed to abandon contract %d: %s"), ctid, strerror(err)); return (-1); } (void) close(fd); return (0); } /* * 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 2004 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include #include #include #include "inetd_impl.h" extern char **environ; static int valid_env_var(const char *var, const char *instance, const char *method) { char *cp = strchr(var, '='); if (cp == NULL || cp == var) { if (method == NULL) return (0); error_msg(gettext("Invalid environment variable \"%s\" for " "method %s of instance %s.\n"), var, method, instance); return (0); } else if (strncmp(var, "SMF_", 4) == 0) { if (method == NULL) return (0); error_msg(gettext("Invalid environment variable \"%s\" for " "method %s of instance %s; \"SMF_\" prefix is reserved.\n"), var, method, instance); return (0); } return (1); } static char ** find_dup(const char *var, char **env, const char *instance, const char *method) { char **p; char *tmp; for (p = env; *p != NULL; p++) { tmp = strchr(*p, '='); assert(tmp != NULL); tmp++; if (strncmp(*p, var, tmp - *p) == 0) break; } if (*p == NULL) return (NULL); error_msg(gettext("Ignoring duplicate environment variable \"%s\" " "for method %s of instance %s.\n"), *p, method, instance); return (p); } /* * Create an environment which is appropriate for spawning an SMF aware * process. * * In order to preserve the correctness of the new environment, various * checks are performed: * * - All SMF_ entries are ignored. All SMF_ entries should be provided * by this function. * - Duplicates in the entry are eliminated. * - Malformed entries are eliminated. * * Detected errors are logged but not fatal, since a single bad entry * should not be enough to prevent an SMF_ functional environment from * being created. */ char ** set_smf_env(struct method_context *mthd_ctxt, instance_t *instance, const char *method) { char **nenv; char **p, **np; size_t nenv_size; /* * Max. of env, three SMF_ variables, and terminating NULL. */ nenv_size = mthd_ctxt->env_sz + 3 + 1; if (instance->config->basic->inherit_env) { for (p = environ; *p != NULL; p++) nenv_size++; } nenv = malloc(sizeof (char *) * nenv_size); if (nenv == NULL) return (NULL); (void) memset(nenv, 0, sizeof (char *) * nenv_size); np = nenv; *np = uu_msprintf("SMF_RESTARTER=%s", INETD_INSTANCE_FMRI); if (*np == NULL) goto fail; else np++; *np = uu_msprintf("SMF_FMRI=%s", instance->fmri); if (*np == NULL) goto fail; else np++; *np = uu_msprintf("SMF_METHOD=%s", method); if (*np == NULL) goto fail; else np++; if (instance->config->basic->inherit_env) { for (p = environ; *p != NULL; p++) { if (!valid_env_var(*p, NULL, NULL)) continue; *np = strdup(*p); if (*np == NULL) goto fail; else np++; } } if (mthd_ctxt->env != NULL) { for (p = mthd_ctxt->env; *p != NULL; p++) { char **dup_pos; if (!valid_env_var(*p, instance->fmri, method)) continue; if ((dup_pos = find_dup(*p, nenv, instance->fmri, method)) != NULL) { free(*dup_pos); *dup_pos = strdup(*p); if (*dup_pos == NULL) goto fail; } else { *np = strdup(*p); if (*np == NULL) goto fail; else np++; } } } *np = NULL; return (nenv); fail: p = nenv; while (nenv_size--) free(*p++); free(nenv); return (NULL); } #! /usr/bin/sh # # 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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Start by cleaning out obsolete instances. For each one that # exists in the repository, remove it. inetd_obsolete_instances=" network/nfs/rquota:ticlts network/nfs/rquota:udp network/rexec:tcp network/rexec:tcp6 network/rpc/gss:ticotsord network/rpc/mdcomm:tcp network/rpc/mdcomm:tcp6 network/rpc/meta:tcp network/rpc/meta:tcp6 network/rpc/metamed:tcp network/rpc/metamed:tcp6 network/rpc/metamh:tcp network/rpc/metamh:tcp6 network/rpc/rex:tcp network/rpc/rstat:ticlts network/rpc/rstat:udp network/rpc/rstat:udp6 network/rpc/rusers:udp network/rpc/rusers:udp6 network/rpc/rusers:ticlts network/rpc/rusers:tcp network/rpc/rusers:tcp6 network/rpc/rusers:ticotsord network/rpc/rusers:ticots network/rpc/spray:ticlts network/rpc/spray:udp network/rpc/spray:udp6 network/rpc/wall:ticlts network/rpc/wall:udp network/rpc/wall:udp6 network/security/krb5_prop:tcp network/security/ktkt_warn:ticotsord network/shell:tcp network/shell:tcp6only platform/sun4u/dcs:tcp platform/sun4u/dcs:tcp6 " for i in $inetd_obsolete_instances; do enable=`svcprop -p general/enabled $i` if [ $? = 0 ]; then # Instance found, so disable and delete svcadm disable $i svccfg delete $i if [ "$enable" = "true" ]; then # Instance was enabled, so enable the replacement. # We must do this here because the profile which # normally enables these is only applied on first # install of smf. s=`echo $i | cut -f1 -d:` svcadm enable $s:default fi fi done # The Following blocks of code cause the inetconv generated services to be # re-generated, so that the latest inetconv modifications are applied to all # services generated by it. inetdconf_entries_file=/tmp/iconf_entries.$$ # Create sed script that prints out inetd.conf src line from inetconv generated # manifest. cat < /tmp/inetd-upgrade.$$.sed /propval name='source_line'/{ n s/'//g p } /from the inetd.conf(5) format line/{ n p } EOF # get list of inetconv generated manifests inetconv_manifests=`/usr/bin/find /lib/svc/manifest -type f -name \*.xml | \ /bin/xargs /bin/grep -l "Generated by inetconv"` # For each inetconv generated manifest determine the instances that should # be disabled when the new manifests are imported, and generate a file with # the inetd.conf entries from all the manifests for consumption by inetconv. > $inetdconf_entries_file inetconv_services="" instances_to_disable="" for manifest in $inetconv_manifests; do manifest_instances=`/sbin/svccfg inventory $manifest | \ egrep "svc:/.*:.*"` manifest_service=`/sbin/svccfg inventory $manifest | \ egrep -v "svc:/.*:.*"` instance_disabled="" default_enabled="" enabled="" for instance in $manifest_instances; do # if the instance doesn't exist in the repository skip it svcprop -q $instance if [ $? -ne 0 ]; then continue fi enabled=`svcprop -p general/enabled $instance` default_instance=`echo $instance | grep ":default"` if [ "$default_instance" != "" ]; then default_enabled=$enabled else # add all non-default instances to disable list instances_to_disable="$instances_to_disable \ $instance" if [ "$enabled" != "true" ]; then instance_disabled="true" fi fi done # if none of the manifest's instances existed, skip this manifest if [ "$enabled" = "" ]; then continue fi # If the default instance existed and was disabled, or if didn't # exist and one of the other instances was disabled, add the default # to the list of instances to disable. if [ "$default_enabled" = "false" -o "$default_enabled" = "" -a \ "$instance_disabled" = "true" ]; then instances_to_disable="$instances_to_disable \ $manifest_service:default" fi # add the manifest's inetd.conf src line to file for inetconv sed -n -f /tmp/inetd-upgrade.$$.sed $manifest >> \ $inetdconf_entries_file done rm /tmp/inetd-upgrade.$$.sed # Check whether we've ever run inetconv before by looking for the # configuration file hash. If we haven't run it before, then we need # to enable services based on inetd.conf. If we have, then the # repository is authoritative. `unimported' will be 0 if the hash exists. svcprop -qp hash svc:/network/inetd:default unimported=$? # Run inetconv on generated file, overwriting previous manifests and values # in repository. /usr/sbin/inetconv -f -i $inetdconf_entries_file # disable the necessary instances for inst in $instances_to_disable; do svcadm disable $inst done # If there is a saved config file from upgrade, use it to enable services, # but only if we're coming from a release that didn't have SMF. saved_config=/etc/inet/inetd.conf.preupgrade if [ $unimported -ne 0 -a -f $saved_config ]; then /usr/sbin/inetconv -e -i $saved_config fi # Now convert the remaining entries in inetd.conf to service manifests /usr/sbin/inetconv # Now disable myself as the upgrade is done svcadm disable network/inetd-upgrade exit 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 (c) 2011 Gary Mills * * Copyright (c) 2004, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * NOTES: To be expanded. * * The SMF inetd. * * Below are some high level notes of the operation of the SMF inetd. The * notes don't go into any real detail, and the viewer of this file is * encouraged to look at the code and its associated comments to better * understand inetd's operation. This saves the potential for the code * and these notes diverging over time. * * Inetd's major work is done from the context of event_loop(). Within this * loop, inetd polls for events arriving from a number of different file * descriptors, representing the following event types, and initiates * any necessary event processing: * - incoming network connections/datagrams. * - notification of terminated processes (discovered via contract events). * - instance specific events originating from the SMF master restarter. * - stop/refresh requests from the inetd method processes (coming in on a * Unix Domain socket). * There's also a timeout set for the poll, which is set to the nearest * scheduled timer in a timer queue that inetd uses to perform delayed * processing, such as bind retries. * The SIGHUP and SIGINT signals can also interrupt the poll, and will * result in inetd being refreshed or stopped respectively, as was the * behavior with the old inetd. * * Inetd implements a state machine for each instance. The states within the * machine are: offline, online, disabled, maintenance, uninitialized and * specializations of the offline state for when an instance exceeds one of * its DOS limits. The state of an instance can be changed as a * result/side-effect of one of the above events occurring, or inetd being * started up. The ongoing state of an instance is stored in the SMF * repository, as required of SMF restarters. This enables an administrator * to view the state of each instance, and, if inetd was to terminate * unexpectedly, it could use the stored state to re-commence where it left off. * * Within the state machine a number of methods are run (if provided) as part * of a state transition to aid/ effect a change in an instance's state. The * supported methods are: offline, online, disable, refresh and start. The * latter of these is the equivalent of the server program and its arguments * in the old inetd. * * Events from the SMF master restarter come in on a number of threads * created in the registration routine of librestart, the delegated restarter * library. These threads call into the restart_event_proxy() function * when an event arrives. To serialize the processing of instances, these events * are then written down a pipe to the process's main thread, which listens * for these events via a poll call, with the file descriptor of the other * end of the pipe in its read set, and processes the event appropriately. * When the event has been processed (which may be delayed if the instance * for which the event is for is in the process of executing one of its methods * as part of a state transition) it writes an acknowledgement back down the * pipe the event was received on. The thread in restart_event_proxy() that * wrote the event will read the acknowledgement it was blocked upon, and will * then be able to return to its caller, thus implicitly acknowledging the * event, and allowing another event to be written down the pipe for the main * thread to process. */ #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 #include #include "inetd_impl.h" /* path to inetd's binary */ #define INETD_PATH "/usr/lib/inet/inetd" /* * inetd's default configuration file paths. /etc/inetd/inetd.conf is set * be be the primary file, so it is checked before /etc/inetd.conf. */ #define PRIMARY_DEFAULT_CONF_FILE "/etc/inet/inetd.conf" #define SECONDARY_DEFAULT_CONF_FILE "/etc/inetd.conf" /* Arguments passed to this binary to request which method to execute. */ #define START_METHOD_ARG "start" #define STOP_METHOD_ARG "stop" #define REFRESH_METHOD_ARG "refresh" /* connection backlog for unix domain socket */ #define UDS_BACKLOG 2 /* number of retries to recv() a request on the UDS socket before giving up */ #define UDS_RECV_RETRIES 10 /* enumeration of the different ends of a pipe */ enum pipe_end { PE_CONSUMER, PE_PRODUCER }; typedef struct { internal_inst_state_t istate; const char *name; restarter_instance_state_t smf_state; instance_method_t method_running; } state_info_t; /* * Collection of information for each state. * NOTE: This table is indexed into using the internal_inst_state_t * enumeration, so the ordering needs to be kept in synch. */ static state_info_t states[] = { {IIS_UNINITIALIZED, "uninitialized", RESTARTER_STATE_UNINIT, IM_NONE}, {IIS_ONLINE, "online", RESTARTER_STATE_ONLINE, IM_START}, {IIS_IN_ONLINE_METHOD, "online_method", RESTARTER_STATE_OFFLINE, IM_ONLINE}, {IIS_OFFLINE, "offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_IN_OFFLINE_METHOD, "offline_method", RESTARTER_STATE_OFFLINE, IM_OFFLINE}, {IIS_DISABLED, "disabled", RESTARTER_STATE_DISABLED, IM_NONE}, {IIS_IN_DISABLE_METHOD, "disabled_method", RESTARTER_STATE_OFFLINE, IM_DISABLE}, {IIS_IN_REFRESH_METHOD, "refresh_method", RESTARTER_STATE_ONLINE, IM_REFRESH}, {IIS_MAINTENANCE, "maintenance", RESTARTER_STATE_MAINT, IM_NONE}, {IIS_OFFLINE_CONRATE, "cr_offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_OFFLINE_BIND, "bind_offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_OFFLINE_COPIES, "copies_offline", RESTARTER_STATE_OFFLINE, IM_NONE}, {IIS_DEGRADED, "degraded", RESTARTER_STATE_DEGRADED, IM_NONE}, {IIS_NONE, "none", RESTARTER_STATE_NONE, IM_NONE} }; /* * Pipe used to send events from the threads created by restarter_bind_handle() * to the main thread of control. */ static int rst_event_pipe[] = {-1, -1}; /* * Used to protect the critical section of code in restarter_event_proxy() that * involves writing an event down the event pipe and reading an acknowledgement. */ static pthread_mutex_t rst_event_pipe_mtx = PTHREAD_MUTEX_INITIALIZER; /* handle used in communication with the master restarter */ static restarter_event_handle_t *rst_event_handle = NULL; /* set to indicate a refresh of inetd is requested */ static boolean_t refresh_inetd_requested = B_FALSE; /* set by the SIGTERM handler to flag we got a SIGTERM */ static boolean_t got_sigterm = B_FALSE; /* * Timer queue used to store timers for delayed event processing, such as * bind retries. */ iu_tq_t *timer_queue = NULL; /* * fd of Unix Domain socket used to communicate stop and refresh requests * to the inetd start method process. */ static int uds_fd = -1; /* * List of inetd's currently managed instances; each containing its state, * and in certain states its configuration. */ static uu_list_pool_t *instance_pool = NULL; uu_list_t *instance_list = NULL; /* set to indicate we're being stopped */ boolean_t inetd_stopping = B_FALSE; /* TCP wrappers syslog globals. Consumed by libwrap. */ int allow_severity = LOG_INFO; int deny_severity = LOG_WARNING; /* path of the configuration file being monitored by check_conf_file() */ static char *conf_file = NULL; /* Auditing session handle */ static adt_session_data_t *audit_handle; /* Number of pending connections */ static size_t tlx_pending_counter; static void uds_fini(void); static int uds_init(void); static int run_method(instance_t *, instance_method_t, const proto_info_t *); static void create_bound_fds(instance_t *); static void destroy_bound_fds(instance_t *); static void destroy_instance(instance_t *); static void inetd_stop(void); static void exec_method(instance_t *instance, instance_method_t method, method_info_t *mi, struct method_context *mthd_ctxt, const proto_info_t *pi) __NORETURN; /* * The following two functions are callbacks that libumem uses to determine * inetd's desired debugging/logging levels. The interface they consume is * exported by FMA and is consolidation private. The comments in the two * functions give the environment variable that will effectively be set to * their returned value, and thus whose behavior for this value, described in * umem_debug(3MALLOC), will be followed. */ const char * _umem_debug_init(void) { return ("default,verbose"); /* UMEM_DEBUG setting */ } const char * _umem_logging_init(void) { return ("fail,contents"); /* UMEM_LOGGING setting */ } static void log_invalid_cfg(const char *fmri) { error_msg(gettext( "Invalid configuration for instance %s, placing in maintenance"), fmri); } /* * Returns B_TRUE if the instance is in a suitable state for inetd to stop. */ static boolean_t instance_stopped(const instance_t *inst) { return ((inst->cur_istate == IIS_OFFLINE) || (inst->cur_istate == IIS_MAINTENANCE) || (inst->cur_istate == IIS_DISABLED) || (inst->cur_istate == IIS_UNINITIALIZED)); } /* * Given the instance fmri, obtain the corresonding scf_instance. * Caller is responsible for freeing the returned scf_instance and * its scf_handle. */ static int fmri_to_instance(char *fmri, scf_instance_t **scf_instp) { int retries, ret = 1; scf_handle_t *h; scf_instance_t *scf_inst; if ((h = scf_handle_create(SCF_VERSION)) == NULL) { error_msg(gettext("Failed to get instance for %s"), fmri); return (1); } if ((scf_inst = scf_instance_create(h)) == NULL) goto out; for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(h) == -1) break; if (scf_handle_decode_fmri(h, fmri, NULL, NULL, scf_inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == 0) { ret = 0; *scf_instp = scf_inst; break; } if (scf_error() != SCF_ERROR_CONNECTION_BROKEN) break; } out: if (ret != 0) { error_msg(gettext("Failed to get instance for %s"), fmri); scf_instance_destroy(scf_inst); scf_handle_destroy(h); } return (ret); } /* * Updates the current and next repository states of instance 'inst'. If * any errors occur an error message is output. */ static void update_instance_states(instance_t *inst, internal_inst_state_t new_cur_state, internal_inst_state_t new_next_state, restarter_error_t err) { internal_inst_state_t old_cur = inst->cur_istate; internal_inst_state_t old_next = inst->next_istate; scf_instance_t *scf_inst = NULL; scf_error_t sret; int ret; restarter_str_t aux = restarter_str_none; /* update the repository/cached internal state */ inst->cur_istate = new_cur_state; inst->next_istate = new_next_state; (void) set_single_rep_val(inst->cur_istate_rep, (int64_t)new_cur_state); (void) set_single_rep_val(inst->next_istate_rep, (int64_t)new_next_state); if (((sret = store_rep_vals(inst->cur_istate_rep, inst->fmri, PR_NAME_CUR_INT_STATE)) != 0) || ((sret = store_rep_vals(inst->next_istate_rep, inst->fmri, PR_NAME_NEXT_INT_STATE)) != 0)) error_msg(gettext("Failed to update state of instance %s in " "repository: %s"), inst->fmri, scf_strerror(sret)); if (fmri_to_instance(inst->fmri, &scf_inst) == 0) { /* * If transitioning to maintenance, check auxiliary_tty set * by svcadm and assign appropriate value to auxiliary_state. * If the maintenance event comes from a service request, * validate auxiliary_fmri and copy it to * restarter/auxiliary_fmri. */ if (new_cur_state == IIS_MAINTENANCE) { if (restarter_inst_ractions_from_tty(scf_inst) == 0) aux = restarter_str_service_request; else aux = restarter_str_administrative_request; } if (aux == restarter_str_service_request) { if (restarter_inst_validate_ractions_aux_fmri( scf_inst) == 0) { if (restarter_inst_set_aux_fmri(scf_inst)) error_msg(gettext("Could not set " "auxiliary_fmri property for %s"), inst->fmri); } else { if (restarter_inst_reset_aux_fmri(scf_inst)) error_msg(gettext("Could not reset " "auxiliary_fmri property for %s"), inst->fmri); } } scf_handle_destroy(scf_instance_handle(scf_inst)); scf_instance_destroy(scf_inst); } /* update the repository SMF state */ if ((ret = restarter_set_states(rst_event_handle, inst->fmri, states[old_cur].smf_state, states[new_cur_state].smf_state, states[old_next].smf_state, states[new_next_state].smf_state, err, aux)) != 0) error_msg(gettext("Failed to update state of instance %s in " "repository: %s"), inst->fmri, strerror(ret)); } void update_state(instance_t *inst, internal_inst_state_t new_cur, restarter_error_t err) { update_instance_states(inst, new_cur, IIS_NONE, err); } /* * Sends a refresh event to the inetd start method process and returns * SMF_EXIT_OK if it managed to send it. If it fails to send the request for * some reason it returns SMF_EXIT_ERR_OTHER. */ static int refresh_method(void) { uds_request_t req = UR_REFRESH_INETD; int fd; if ((fd = connect_to_inetd()) < 0) { error_msg(gettext("Failed to connect to inetd: %s"), strerror(errno)); return (SMF_EXIT_ERR_OTHER); } /* write the request and return success */ if (safe_write(fd, &req, sizeof (req)) == -1) { error_msg( gettext("Failed to send refresh request to inetd: %s"), strerror(errno)); (void) close(fd); return (SMF_EXIT_ERR_OTHER); } (void) close(fd); return (SMF_EXIT_OK); } /* * Sends a stop event to the inetd start method process and wait till it goes * away. If inetd is determined to have stopped SMF_EXIT_OK is returned, else * SMF_EXIT_ERR_OTHER is returned. */ static int stop_method(void) { uds_request_t req = UR_STOP_INETD; int fd; char c; ssize_t ret; if ((fd = connect_to_inetd()) == -1) { debug_msg(gettext("Failed to connect to inetd: %s"), strerror(errno)); /* * Assume connect_to_inetd() failed because inetd was already * stopped, and return success. */ return (SMF_EXIT_OK); } /* * This is safe to do since we're fired off in a separate process * than inetd and in the case we get wedged, the stop method timeout * will occur and we'd be killed by our restarter. */ enable_blocking(fd); /* write the stop request to inetd and wait till it goes away */ if (safe_write(fd, &req, sizeof (req)) != 0) { error_msg(gettext("Failed to send stop request to inetd")); (void) close(fd); return (SMF_EXIT_ERR_OTHER); } /* wait until remote end of socket is closed */ while (((ret = recv(fd, &c, sizeof (c), 0)) != 0) && (errno == EINTR)) ; (void) close(fd); if (ret != 0) { error_msg(gettext("Failed to determine whether inetd stopped")); return (SMF_EXIT_ERR_OTHER); } return (SMF_EXIT_OK); } /* * This function is called to handle restarter events coming in from the * master restarter. It is registered with the master restarter via * restarter_bind_handle() and simply passes a pointer to the event down * the event pipe, which will be discovered by the poll in the event loop * and processed there. It waits for an acknowledgement to be written back down * the pipe before returning. * Writing a pointer to the function's 'event' parameter down the pipe will * be safe, as the thread in restarter_event_proxy() doesn't return until * the main thread has finished its processing of the passed event, thus * the referenced event will remain around until the function returns. * To impose the limit of only one event being in the pipe and processed * at once, a lock is taken on entry to this function and returned on exit. * Always returns 0. */ static int restarter_event_proxy(restarter_event_t *event) { boolean_t processed; (void) pthread_mutex_lock(&rst_event_pipe_mtx); /* write the event to the main worker thread down the pipe */ if (safe_write(rst_event_pipe[PE_PRODUCER], &event, sizeof (event)) != 0) goto pipe_error; /* * Wait for an acknowledgement that the event has been processed from * the same pipe. In the case that inetd is stopping, any thread in * this function will simply block on this read until inetd eventually * exits. This will result in this function not returning success to * its caller, and the event that was being processed when the * function exited will be re-sent when inetd is next started. */ if (safe_read(rst_event_pipe[PE_PRODUCER], &processed, sizeof (processed)) != 0) goto pipe_error; (void) pthread_mutex_unlock(&rst_event_pipe_mtx); return (processed ? 0 : EAGAIN); pipe_error: /* * Something's seriously wrong with the event pipe. Notify the * worker thread by closing this end of the event pipe and pause till * inetd exits. */ error_msg(gettext("Can't process restarter events: %s"), strerror(errno)); (void) close(rst_event_pipe[PE_PRODUCER]); for (;;) (void) pause(); /* NOTREACHED */ } /* * Let restarter_event_proxy() know we're finished with the event it's blocked * upon. The 'processed' argument denotes whether we successfully processed the * event. */ static void ack_restarter_event(boolean_t processed) { /* * If safe_write returns -1 something's seriously wrong with the event * pipe, so start the shutdown proceedings. */ if (safe_write(rst_event_pipe[PE_CONSUMER], &processed, sizeof (processed)) == -1) inetd_stop(); } /* * Switch the syslog identification string to 'ident'. */ static void change_syslog_ident(const char *ident) { closelog(); openlog(ident, LOG_PID|LOG_CONS, LOG_DAEMON); } /* * Perform TCP wrappers checks on this instance. Due to the fact that the * current wrappers code used in Solaris is taken untouched from the open * source version, we're stuck with using the daemon name for the checks, as * opposed to making use of instance FMRIs. Sigh. * Returns B_TRUE if the check passed, else B_FALSE. */ static boolean_t tcp_wrappers_ok(instance_t *instance) { boolean_t rval = B_TRUE; char *daemon_name; basic_cfg_t *cfg = instance->config->basic; struct request_info req; /* * Wrap the service using libwrap functions. The code below implements * the functionality of tcpd. This is done only for stream,nowait * services, following the convention of other vendors. udp/dgram and * stream/wait can NOT be wrapped with this libwrap, so be wary of * changing the test below. */ if (cfg->do_tcp_wrappers && !cfg->iswait && !cfg->istlx) { daemon_name = instance->config->methods[ IM_START]->exec_args_we.we_wordv[0]; if (*daemon_name == '/') daemon_name = strrchr(daemon_name, '/') + 1; /* * Change the syslog message identity to the name of the * daemon being wrapped, as opposed to "inetd". */ change_syslog_ident(daemon_name); (void) request_init(&req, RQ_DAEMON, daemon_name, RQ_FILE, instance->conn_fd, NULL); fromhost(&req); if (strcasecmp(eval_hostname(req.client), paranoid) == 0) { syslog(deny_severity, "refused connect from %s (name/address mismatch)", eval_client(&req)); if (req.sink != NULL) req.sink(instance->conn_fd); rval = B_FALSE; } else if (!hosts_access(&req)) { syslog(deny_severity, "refused connect from %s (access denied)", eval_client(&req)); if (req.sink != NULL) req.sink(instance->conn_fd); rval = B_FALSE; } else { syslog(allow_severity, "connect from %s", eval_client(&req)); } /* Revert syslog identity back to "inetd". */ change_syslog_ident(SYSLOG_IDENT); } return (rval); } /* * Handler registered with the timer queue code to remove an instance from * the connection rate offline state when it has been there for its allotted * time. */ /* ARGSUSED */ static void conn_rate_online(iu_tq_t *tq, void *arg) { instance_t *instance = arg; assert(instance->cur_istate == IIS_OFFLINE_CONRATE); instance->timer_id = -1; update_state(instance, IIS_OFFLINE, RERR_RESTART); process_offline_inst(instance); } /* * Check whether this instance in the offline state is in transition to * another state and do the work to continue this transition. */ void process_offline_inst(instance_t *inst) { if (inst->disable_req) { inst->disable_req = B_FALSE; (void) run_method(inst, IM_DISABLE, NULL); } else if (inst->maintenance_req) { inst->maintenance_req = B_FALSE; update_state(inst, IIS_MAINTENANCE, RERR_RESTART); /* * If inetd is in the process of stopping, we don't want to enter * any states but offline, disabled and maintenance. */ } else if (!inetd_stopping) { if (inst->conn_rate_exceeded) { basic_cfg_t *cfg = inst->config->basic; inst->conn_rate_exceeded = B_FALSE; update_state(inst, IIS_OFFLINE_CONRATE, RERR_RESTART); /* * Schedule a timer to bring the instance out of the * connection rate offline state. */ inst->timer_id = iu_schedule_timer(timer_queue, cfg->conn_rate_offline, conn_rate_online, inst); if (inst->timer_id == -1) { error_msg(gettext("%s unable to set timer, " "won't be brought on line after %d " "seconds."), inst->fmri, cfg->conn_rate_offline); } } else if (copies_limit_exceeded(inst)) { update_state(inst, IIS_OFFLINE_COPIES, RERR_RESTART); } } } /* * Create a socket bound to the instance's configured address. If the * bind fails, returns -1, else the fd of the bound socket. */ static int create_bound_socket(const instance_t *inst, socket_info_t *sock_info) { int fd; int on = 1; const char *fmri = inst->fmri; rpc_info_t *rpc = sock_info->pr_info.ri; const char *proto = sock_info->pr_info.proto; fd = socket(sock_info->local_addr.ss_family, sock_info->type, sock_info->protocol); if (fd < 0) { error_msg(gettext( "Socket creation failure for instance %s, proto %s: %s"), fmri, proto, strerror(errno)); return (-1); } if (setsockopt(fd, SOL_SOCKET, SO_REUSEADDR, &on, sizeof (on)) == -1) { error_msg(gettext("setsockopt SO_REUSEADDR failed for service " "instance %s, proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } if (inst->config->basic->do_tcp_keepalive && !inst->config->basic->iswait && !inst->config->basic->istlx) { /* set the keepalive option */ if (setsockopt(fd, SOL_SOCKET, SO_KEEPALIVE, &on, sizeof (on)) == -1) { error_msg(gettext("setsockopt SO_KEEPALIVE failed for " "service instance %s, proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } } if (sock_info->pr_info.v6only) { /* restrict socket to IPv6 communications only */ if (setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof (on)) == -1) { error_msg(gettext("setsockopt IPV6_V6ONLY failed for " "service instance %s, proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } } if (rpc != NULL) SS_SETPORT(sock_info->local_addr, 0); if (bind(fd, (struct sockaddr *)&(sock_info->local_addr), SS_ADDRLEN(sock_info->local_addr)) < 0) { error_msg(gettext( "Failed to bind to the port of service instance %s, " "proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } /* * Retrieve and store the address bound to for RPC services. */ if (rpc != NULL) { struct sockaddr_storage ss; int ss_size = sizeof (ss); if (getsockname(fd, (struct sockaddr *)&ss, &ss_size) < 0) { error_msg(gettext("Failed getsockname for instance %s, " "proto %s: %s"), fmri, proto, strerror(errno)); (void) close(fd); return (-1); } (void) memcpy(rpc->netbuf.buf, &ss, sizeof (struct sockaddr_storage)); rpc->netbuf.len = SS_ADDRLEN(ss); rpc->netbuf.maxlen = SS_ADDRLEN(ss); } if (sock_info->type == SOCK_STREAM) { int qlen = inst->config->basic->conn_backlog; debug_msg("Listening for service %s with backlog queue" " size %d", fmri, qlen); (void) listen(fd, qlen); } return (fd); } /* * Handler registered with the timer queue code to retry the creation * of a bound fd. */ /* ARGSUSED */ static void retry_bind(iu_tq_t *tq, void *arg) { instance_t *instance = arg; switch (instance->cur_istate) { case IIS_OFFLINE_BIND: case IIS_ONLINE: case IIS_DEGRADED: case IIS_IN_ONLINE_METHOD: case IIS_IN_REFRESH_METHOD: break; default: #ifndef NDEBUG (void) fprintf(stderr, "%s:%d: Unknown instance state %d.\n", __FILE__, __LINE__, instance->cur_istate); #endif abort(); } instance->bind_timer_id = -1; create_bound_fds(instance); } /* * For each of the fds for the given instance that are bound, if 'listen' is * set add them to the poll set, else remove them from it. If proto_name is * not NULL then apply the change only to this specific protocol endpoint. * If any additions fail, returns -1, else 0 on success. */ int poll_bound_fds(instance_t *instance, boolean_t listen, char *proto_name) { basic_cfg_t *cfg = instance->config->basic; proto_info_t *pi; int ret = 0; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->listen_fd != -1) { /* fd bound */ if (proto_name == NULL || strcmp(pi->proto, proto_name) == 0) { if (listen == B_FALSE) { clear_pollfd(pi->listen_fd); } else if (set_pollfd(pi->listen_fd, POLLIN) == -1) { ret = -1; } } } } return (ret); } /* * Handle the case were we either fail to create a bound fd or we fail * to add a bound fd to the poll set for the given instance. */ static void handle_bind_failure(instance_t *instance) { basic_cfg_t *cfg = instance->config->basic; /* * We must be being called as a result of a failed poll_bound_fds() * as a bind retry is already scheduled. Just return and let it do * the work. */ if (instance->bind_timer_id != -1) return; /* * Check if the rebind retries limit is operative and if so, * if it has been reached. */ if (((cfg->bind_fail_interval <= 0) || /* no retries */ ((cfg->bind_fail_max >= 0) && /* limit reached */ (++instance->bind_fail_count > cfg->bind_fail_max))) || ((instance->bind_timer_id = iu_schedule_timer(timer_queue, cfg->bind_fail_interval, retry_bind, instance)) == -1)) { proto_info_t *pi; instance->bind_fail_count = 0; switch (instance->cur_istate) { case IIS_DEGRADED: case IIS_ONLINE: /* check if any of the fds are being poll'd upon */ for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if ((pi->listen_fd != -1) && (find_pollfd(pi->listen_fd) != NULL)) break; } if (pi != NULL) { /* polling on > 0 fds */ warn_msg(gettext("Failed to bind on " "all protocols for instance %s, " "transitioning to degraded"), instance->fmri); update_state(instance, IIS_DEGRADED, RERR_NONE); instance->bind_retries_exceeded = B_TRUE; break; } destroy_bound_fds(instance); /* * In the case we failed the 'bind' because set_pollfd() * failed on all bound fds, use the offline handling. */ /* FALLTHROUGH */ case IIS_OFFLINE: case IIS_OFFLINE_BIND: error_msg(gettext("Too many bind failures for instance " "%s, transitioning to maintenance"), instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); break; case IIS_IN_ONLINE_METHOD: case IIS_IN_REFRESH_METHOD: warn_msg(gettext("Failed to bind on all " "protocols for instance %s, instance will go to " "degraded"), instance->fmri); /* * Set the retries exceeded flag so when the method * completes the instance goes to the degraded state. */ instance->bind_retries_exceeded = B_TRUE; break; default: #ifndef NDEBUG (void) fprintf(stderr, "%s:%d: Unknown instance state %d.\n", __FILE__, __LINE__, instance->cur_istate); #endif abort(); } } else if (instance->cur_istate == IIS_OFFLINE) { /* * bind re-scheduled, so if we're offline reflect this in the * state. */ update_state(instance, IIS_OFFLINE_BIND, RERR_NONE); } } /* * Check if two transport protocols for RPC conflict. */ boolean_t is_rpc_proto_conflict(const char *proto0, const char *proto1) { if (strcmp(proto0, "tcp") == 0) { if (strcmp(proto1, "tcp") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "tcp6") == 0) { if (strcmp(proto1, "tcp") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6only") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "tcp6only") == 0) { if (strcmp(proto1, "tcp6only") == 0) return (B_TRUE); if (strcmp(proto1, "tcp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "udp") == 0) { if (strcmp(proto1, "udp") == 0) return (B_TRUE); if (strcmp(proto1, "udp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "udp6") == 0) { if (strcmp(proto1, "udp") == 0) return (B_TRUE); if (strcmp(proto1, "udp6only") == 0) return (B_TRUE); if (strcmp(proto1, "udp6") == 0) return (B_TRUE); return (B_FALSE); } if (strcmp(proto0, "udp6only") == 0) { if (strcmp(proto1, "udp6only") == 0) return (B_TRUE); if (strcmp(proto1, "udp6") == 0) return (B_TRUE); return (0); } /* * If the protocol isn't TCP/IP or UDP/IP assume that it has its own * port namepsace and that conflicts can be detected by literal string * comparison. */ if (strcmp(proto0, proto1)) return (FALSE); return (B_TRUE); } /* * Check if inetd thinks this RPC program number is already registered. * * An RPC protocol conflict occurs if * a) the program numbers are the same and, * b) the version numbers overlap, * c) the protocols (TCP vs UDP vs tic*) are the same. */ boolean_t is_rpc_num_in_use(int rpc_n, char *proto, int lowver, int highver) { instance_t *i; basic_cfg_t *cfg; proto_info_t *pi; for (i = uu_list_first(instance_list); i != NULL; i = uu_list_next(instance_list, i)) { if (i->cur_istate != IIS_ONLINE) continue; cfg = i->config->basic; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->ri == NULL) continue; if (pi->ri->prognum != rpc_n) continue; if (!is_rpc_proto_conflict(pi->proto, proto)) continue; if ((lowver < pi->ri->lowver && highver < pi->ri->lowver) || (lowver > pi->ri->highver && highver > pi->ri->highver)) continue; return (B_TRUE); } } return (B_FALSE); } /* * Independent of the transport, for each of the entries in the instance's * proto list this function first attempts to create an associated network fd; * for RPC services these are then bound to a kernel chosen port and the * fd is registered with rpcbind; for non-RPC services the fds are bound * to the port associated with the instance's service name. On any successful * binds the instance is taken online. Failed binds are handled by * handle_bind_failure(). */ void create_bound_fds(instance_t *instance) { basic_cfg_t *cfg = instance->config->basic; boolean_t failure = B_FALSE; boolean_t success = B_FALSE; proto_info_t *pi; /* * Loop through and try and bind any unbound protos. */ for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->listen_fd != -1) continue; if (cfg->istlx) { pi->listen_fd = create_bound_endpoint(instance, (tlx_info_t *)pi); } else { /* * We cast pi to a void so we can then go on to cast * it to a socket_info_t without lint complaining * about alignment. This is done because the x86 * version of lint thinks a lint suppression directive * is unnecessary and flags it as such, yet the sparc * version complains if it's absent. */ void *p = pi; pi->listen_fd = create_bound_socket(instance, (socket_info_t *)p); } if (pi->listen_fd == -1) { failure = B_TRUE; continue; } if (pi->ri != NULL) { /* * Don't register the same RPC program number twice. * Doing so silently discards the old service * without causing an error. */ if (is_rpc_num_in_use(pi->ri->prognum, pi->proto, pi->ri->lowver, pi->ri->highver)) { failure = B_TRUE; close_net_fd(instance, pi->listen_fd); pi->listen_fd = -1; continue; } unregister_rpc_service(instance->fmri, pi->ri); if (register_rpc_service(instance->fmri, pi->ri) == -1) { close_net_fd(instance, pi->listen_fd); pi->listen_fd = -1; failure = B_TRUE; continue; } } success = B_TRUE; } switch (instance->cur_istate) { case IIS_OFFLINE: case IIS_OFFLINE_BIND: /* * If we've managed to bind at least one proto lets run the * online method, so we can start listening for it. */ if (success && run_method(instance, IM_ONLINE, NULL) == -1) return; /* instance gone to maintenance */ break; case IIS_ONLINE: case IIS_IN_REFRESH_METHOD: /* * We're 'online', so start polling on any bound fds we're * currently not. */ if (poll_bound_fds(instance, B_TRUE, NULL) != 0) { failure = B_TRUE; } else if (!failure) { /* * We've successfully bound and poll'd upon all protos, * so reset the failure count. */ instance->bind_fail_count = 0; } break; case IIS_IN_ONLINE_METHOD: /* * Nothing to do here as the method completion code will start * listening for any successfully bound fds. */ break; default: #ifndef NDEBUG (void) fprintf(stderr, "%s:%d: Unknown instance state %d.\n", __FILE__, __LINE__, instance->cur_istate); #endif abort(); } if (failure) handle_bind_failure(instance); } /* * Counter to create_bound_fds(), for each of the bound network fds this * function unregisters the instance from rpcbind if it's an RPC service, * stops listening for new connections for it and then closes the listening fd. */ static void destroy_bound_fds(instance_t *instance) { basic_cfg_t *cfg = instance->config->basic; proto_info_t *pi; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (pi->listen_fd != -1) { if (pi->ri != NULL) unregister_rpc_service(instance->fmri, pi->ri); clear_pollfd(pi->listen_fd); close_net_fd(instance, pi->listen_fd); pi->listen_fd = -1; } } /* cancel any bind retries */ if (instance->bind_timer_id != -1) cancel_bind_timer(instance); instance->bind_retries_exceeded = B_FALSE; } /* * Perform %A address expansion and return a pointer to a static string * array containing crafted arguments. This expansion is provided for * compatibility with 4.2BSD daemons, and as such we've copied the logic of * the legacy inetd to maintain this compatibility as much as possible. This * logic is a bit scatty, but it dates back at least as far as SunOS 4.x. */ static char ** expand_address(instance_t *inst, const proto_info_t *pi) { static char addrbuf[sizeof ("ffffffff.65536")]; static char *ret[3]; instance_cfg_t *cfg = inst->config; /* * We cast pi to a void so we can then go on to cast it to a * socket_info_t without lint complaining about alignment. This * is done because the x86 version of lint thinks a lint suppression * directive is unnecessary and flags it as such, yet the sparc * version complains if it's absent. */ const void *p = pi; /* set ret[0] to the basename of exec path */ if ((ret[0] = strrchr(cfg->methods[IM_START]->exec_path, '/')) != NULL) { ret[0]++; } else { ret[0] = cfg->methods[IM_START]->exec_path; } if (!cfg->basic->istlx && (((socket_info_t *)p)->type == SOCK_DGRAM)) { ret[1] = NULL; } else { addrbuf[0] = '\0'; if (!cfg->basic->iswait && (inst->remote_addr.ss_family == AF_INET)) { struct sockaddr_in *sp; sp = (struct sockaddr_in *)&(inst->remote_addr); (void) snprintf(addrbuf, sizeof (addrbuf), "%x.%hu", ntohl(sp->sin_addr.s_addr), ntohs(sp->sin_port)); } ret[1] = addrbuf; ret[2] = NULL; } return (ret); } /* * Returns the state associated with the supplied method being run for an * instance. */ static internal_inst_state_t get_method_state(instance_method_t method) { state_info_t *sip; for (sip = states; sip->istate != IIS_NONE; sip++) { if (sip->method_running == method) break; } assert(sip->istate != IIS_NONE); return (sip->istate); } /* * Store the method's PID and CID in the repository. If the store fails * we ignore it and just drive on. */ static void add_method_ids(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd) { if (cid != -1) (void) add_remove_contract(ins, B_TRUE, cid); if (mthd == IM_START) { if (add_rep_val(ins->start_pids, (int64_t)pid) == 0) { (void) store_rep_vals(ins->start_pids, ins->fmri, PR_NAME_START_PIDS); } } else { if (add_rep_val(ins->non_start_pid, (int64_t)pid) == 0) { (void) store_rep_vals(ins->non_start_pid, ins->fmri, PR_NAME_NON_START_PID); } } } /* * Remove the method's PID and CID from the repository. If the removal * fails we ignore it and drive on. */ void remove_method_ids(instance_t *inst, pid_t pid, ctid_t cid, instance_method_t mthd) { if (cid != -1) (void) add_remove_contract(inst, B_FALSE, cid); if (mthd == IM_START) { remove_rep_val(inst->start_pids, (int64_t)pid); (void) store_rep_vals(inst->start_pids, inst->fmri, PR_NAME_START_PIDS); } else { remove_rep_val(inst->non_start_pid, (int64_t)pid); (void) store_rep_vals(inst->non_start_pid, inst->fmri, PR_NAME_NON_START_PID); } } static instance_t * create_instance(const char *fmri) { instance_t *ret; if (((ret = calloc(1, sizeof (instance_t))) == NULL) || ((ret->fmri = strdup(fmri)) == NULL)) goto alloc_fail; ret->conn_fd = -1; ret->copies = 0; ret->conn_rate_count = 0; ret->fail_rate_count = 0; ret->bind_fail_count = 0; if (((ret->non_start_pid = create_rep_val_list()) == NULL) || ((ret->start_pids = create_rep_val_list()) == NULL) || ((ret->start_ctids = create_rep_val_list()) == NULL)) goto alloc_fail; ret->cur_istate = IIS_NONE; ret->next_istate = IIS_NONE; if (((ret->cur_istate_rep = create_rep_val_list()) == NULL) || ((ret->next_istate_rep = create_rep_val_list()) == NULL)) goto alloc_fail; ret->config = NULL; ret->new_config = NULL; ret->timer_id = -1; ret->bind_timer_id = -1; ret->disable_req = B_FALSE; ret->maintenance_req = B_FALSE; ret->conn_rate_exceeded = B_FALSE; ret->bind_retries_exceeded = B_FALSE; ret->pending_rst_event = RESTARTER_EVENT_TYPE_INVALID; return (ret); alloc_fail: error_msg(strerror(errno)); destroy_instance(ret); return (NULL); } static void destroy_instance(instance_t *inst) { if (inst == NULL) return; destroy_instance_cfg(inst->config); destroy_instance_cfg(inst->new_config); destroy_rep_val_list(inst->cur_istate_rep); destroy_rep_val_list(inst->next_istate_rep); destroy_rep_val_list(inst->start_pids); destroy_rep_val_list(inst->non_start_pid); destroy_rep_val_list(inst->start_ctids); free(inst->fmri); free(inst); } /* * Retrieves the current and next states internal states. Returns 0 on success, * else returns one of the following on error: * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. * SCF_ERROR_TYPE_MISMATCH if the property was of an unexpected type. * SCF_ERROR_NO_RESOURCES if the server doesn't have adequate resources. * SCF_ERROR_NO_SERVER if the server isn't running. */ static scf_error_t retrieve_instance_state(instance_t *inst) { scf_error_t ret; /* retrieve internal states */ if (((ret = retrieve_rep_vals(inst->cur_istate_rep, inst->fmri, PR_NAME_CUR_INT_STATE)) != 0) || ((ret = retrieve_rep_vals(inst->next_istate_rep, inst->fmri, PR_NAME_NEXT_INT_STATE)) != 0)) { if (ret != SCF_ERROR_NOT_FOUND) { error_msg(gettext( "Failed to read state of instance %s: %s"), inst->fmri, scf_strerror(scf_error())); return (ret); } debug_msg("instance with no previous int state - " "setting state to uninitialized"); if ((set_single_rep_val(inst->cur_istate_rep, (int64_t)IIS_UNINITIALIZED) == -1) || (set_single_rep_val(inst->next_istate_rep, (int64_t)IIS_NONE) == -1)) { return (SCF_ERROR_NO_MEMORY); } } /* update convenience states */ inst->cur_istate = get_single_rep_val(inst->cur_istate_rep); inst->next_istate = get_single_rep_val(inst->next_istate_rep); return (0); } /* * Retrieve stored process ids and register each of them so we process their * termination. */ static int retrieve_method_pids(instance_t *inst) { rep_val_t *rv; switch (retrieve_rep_vals(inst->start_pids, inst->fmri, PR_NAME_START_PIDS)) { case 0: break; case SCF_ERROR_NOT_FOUND: return (0); default: error_msg(gettext("Failed to retrieve the start pids of " "instance %s from repository: %s"), inst->fmri, scf_strerror(scf_error())); return (-1); } rv = uu_list_first(inst->start_pids); while (rv != NULL) { if (register_method(inst, (pid_t)rv->val, (ctid_t)-1, IM_START, NULL) == 0) { inst->copies++; rv = uu_list_next(inst->start_pids, rv); } else if (errno == ENOENT) { pid_t pid = (pid_t)rv->val; /* * The process must have already terminated. Remove * it from the list. */ rv = uu_list_next(inst->start_pids, rv); remove_rep_val(inst->start_pids, pid); } else { error_msg(gettext("Failed to listen for the completion " "of %s method of instance %s"), START_METHOD_NAME, inst->fmri); rv = uu_list_next(inst->start_pids, rv); } } /* synch the repository pid list to remove any terminated pids */ (void) store_rep_vals(inst->start_pids, inst->fmri, PR_NAME_START_PIDS); return (0); } /* * Remove the passed instance from inetd control. */ static void remove_instance(instance_t *instance) { switch (instance->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: /* stop listening for network connections */ destroy_bound_fds(instance); break; case IIS_OFFLINE_BIND: cancel_bind_timer(instance); break; case IIS_OFFLINE_CONRATE: cancel_inst_timer(instance); break; } /* stop listening for terminated methods */ unregister_instance_methods(instance); uu_list_remove(instance_list, instance); destroy_instance(instance); } /* * Refresh the configuration of instance 'inst'. This method gets called as * a result of a refresh event for the instance from the master restarter, so * we can rely upon the instance's running snapshot having been updated from * its configuration snapshot. */ void refresh_instance(instance_t *inst) { instance_cfg_t *cfg; switch (inst->cur_istate) { case IIS_MAINTENANCE: case IIS_DISABLED: case IIS_UNINITIALIZED: /* * Ignore any possible changes, we'll re-read the configuration * automatically when we exit these states. */ break; case IIS_OFFLINE_COPIES: case IIS_OFFLINE_BIND: case IIS_OFFLINE: case IIS_OFFLINE_CONRATE: destroy_instance_cfg(inst->config); if ((inst->config = read_instance_cfg(inst->fmri)) == NULL) { log_invalid_cfg(inst->fmri); if (inst->cur_istate == IIS_OFFLINE_BIND) { cancel_bind_timer(inst); } else if (inst->cur_istate == IIS_OFFLINE_CONRATE) { cancel_inst_timer(inst); } update_state(inst, IIS_MAINTENANCE, RERR_FAULT); } else { switch (inst->cur_istate) { case IIS_OFFLINE_BIND: if (copies_limit_exceeded(inst)) { /* Cancel scheduled bind retries. */ cancel_bind_timer(inst); /* * Take the instance to the copies * offline state, via the offline * state. */ update_state(inst, IIS_OFFLINE, RERR_RESTART); process_offline_inst(inst); } break; case IIS_OFFLINE: process_offline_inst(inst); break; case IIS_OFFLINE_CONRATE: /* * Since we're already in a DOS state, * don't bother evaluating the copies * limit. This will be evaluated when * we leave this state in * process_offline_inst(). */ break; case IIS_OFFLINE_COPIES: /* * Check if the copies limit has been increased * above the current count. */ if (!copies_limit_exceeded(inst)) { update_state(inst, IIS_OFFLINE, RERR_RESTART); process_offline_inst(inst); } break; default: assert(0); } } break; case IIS_DEGRADED: case IIS_ONLINE: if ((cfg = read_instance_cfg(inst->fmri)) != NULL) { instance_cfg_t *ocfg = inst->config; /* * Try to avoid the overhead of taking an instance * offline and back on again. We do this by limiting * this behavior to two eventualities: * - there needs to be a re-bind to listen on behalf * of the instance with its new configuration. This * could be because for example its service has been * associated with a different port, or because the * v6only protocol option has been newly applied to * the instance. * - one or both of the start or online methods of the * instance have changed in the new configuration. * Without taking the instance offline when the * start method changed the instance may be running * with unwanted parameters (or event an unwanted * binary); and without taking the instance offline * if its online method was to change, some part of * its running environment may have changed and would * not be picked up until the instance next goes * offline for another reason. */ if ((!bind_config_equal(ocfg->basic, cfg->basic)) || !method_info_equal(ocfg->methods[IM_ONLINE], cfg->methods[IM_ONLINE]) || !method_info_equal(ocfg->methods[IM_START], cfg->methods[IM_START])) { destroy_bound_fds(inst); assert(inst->new_config == NULL); inst->new_config = cfg; (void) run_method(inst, IM_OFFLINE, NULL); } else { /* no bind config / method changes */ /* * swap the proto list over from the old * configuration to the new, so we retain * our set of network fds. */ destroy_proto_list(cfg->basic); cfg->basic->proto_list = ocfg->basic->proto_list; ocfg->basic->proto_list = NULL; destroy_instance_cfg(ocfg); inst->config = cfg; /* re-evaluate copies limits based on new cfg */ if (copies_limit_exceeded(inst)) { destroy_bound_fds(inst); (void) run_method(inst, IM_OFFLINE, NULL); } else { /* * Since the instance isn't being * taken offline, where we assume it * would pick-up any configuration * changes automatically when it goes * back online, run its refresh method * to allow it to pick-up any changes * whilst still online. */ (void) run_method(inst, IM_REFRESH, NULL); } } } else { log_invalid_cfg(inst->fmri); destroy_bound_fds(inst); inst->maintenance_req = B_TRUE; (void) run_method(inst, IM_OFFLINE, NULL); } break; default: debug_msg("Unhandled current state %d for instance in " "refresh_instance", inst->cur_istate); assert(0); } } /* * Called by process_restarter_event() to handle a restarter event for an * instance. */ static void handle_restarter_event(instance_t *instance, restarter_event_type_t event, boolean_t send_ack) { switch (event) { case RESTARTER_EVENT_TYPE_ADD_INSTANCE: /* * When startd restarts, it sends _ADD_INSTANCE to delegated * restarters for all those services managed by them. We should * acknowledge this event, as startd's graph needs to be updated * about the current state of the service, when startd is * restarting. * update_state() is ok to be called here, as commands for * instances in transition are deferred by * process_restarter_event(). */ update_state(instance, instance->cur_istate, RERR_NONE); goto done; case RESTARTER_EVENT_TYPE_ADMIN_REFRESH: refresh_instance(instance); goto done; case RESTARTER_EVENT_TYPE_ADMIN_RESTART: /* * We've got a restart event, so if the instance is online * in any way initiate taking it offline, and rely upon * our restarter to send us an online event to bring * it back online. */ switch (instance->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: destroy_bound_fds(instance); (void) run_method(instance, IM_OFFLINE, NULL); } goto done; case RESTARTER_EVENT_TYPE_REMOVE_INSTANCE: remove_instance(instance); goto done; case RESTARTER_EVENT_TYPE_STOP_RESET: case RESTARTER_EVENT_TYPE_STOP: switch (instance->cur_istate) { case IIS_OFFLINE_CONRATE: case IIS_OFFLINE_BIND: case IIS_OFFLINE_COPIES: /* * inetd must be closing down as we wouldn't get this * event in one of these states from the master * restarter. Take the instance to the offline resting * state. */ if (instance->cur_istate == IIS_OFFLINE_BIND) { cancel_bind_timer(instance); } else if (instance->cur_istate == IIS_OFFLINE_CONRATE) { cancel_inst_timer(instance); } update_state(instance, IIS_OFFLINE, RERR_RESTART); goto done; } break; } switch (instance->cur_istate) { case IIS_OFFLINE: switch (event) { case RESTARTER_EVENT_TYPE_START: /* * Dependencies are met, let's take the service online. * Only try and bind for a wait type service if * no process is running on its behalf. Otherwise, just * mark the service online and binding will be attempted * when the process exits. */ if (!(instance->config->basic->iswait && (uu_list_first(instance->start_pids) != NULL))) { create_bound_fds(instance); } else { update_state(instance, IIS_ONLINE, RERR_NONE); } break; case RESTARTER_EVENT_TYPE_DISABLE: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The instance should be disabled, so run the * instance's disabled method that will do the work * to take it there. */ (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance * go to maintenance; since we're already offline * just update the state to the maintenance state. */ update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_OFFLINE_BIND: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The instance should be disabled. Firstly, as for * the above dependencies unmet comment, cancel * the bind retry timer and update the state to * offline. Then, run the disable method to do the * work to take the instance from offline to * disabled. */ cancel_bind_timer(instance); update_state(instance, IIS_OFFLINE, RERR_RESTART); (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance * be placed in the maintenance state. Cancel the * outstanding retry timer, and since we're already * offline, update the state to maintenance. */ cancel_bind_timer(instance); update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_DEGRADED: case IIS_ONLINE: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The instance needs to be disabled. Do the same work * as for the dependencies unmet event below to * take the instance offline. */ destroy_bound_fds(instance); /* * Indicate that the offline method is being run * as part of going to the disabled state, and to * carry on this transition. */ instance->disable_req = B_TRUE; (void) run_method(instance, IM_OFFLINE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance be * placed in the maintenance state. This involves * firstly taking the service offline, so do the * same work as for the dependencies unmet event * below. We set the maintenance_req flag to * indicate that when we get to the offline state * we should be placed directly into the maintenance * state. */ instance->maintenance_req = B_TRUE; /* FALLTHROUGH */ case RESTARTER_EVENT_TYPE_STOP_RESET: case RESTARTER_EVENT_TYPE_STOP: /* * Dependencies have become unmet. Close and * stop listening on the instance's network file * descriptor, and run the offline method to do * any work required to take us to the offline state. */ destroy_bound_fds(instance); (void) run_method(instance, IM_OFFLINE, NULL); } break; case IIS_UNINITIALIZED: if (event == RESTARTER_EVENT_TYPE_DISABLE || event == RESTARTER_EVENT_TYPE_ADMIN_DISABLE) { update_state(instance, IIS_DISABLED, RERR_NONE); break; } else if (event != RESTARTER_EVENT_TYPE_ENABLE) { /* * Ignore other events until we know whether we're * enabled or not. */ break; } /* * We've got an enabled event; make use of the handling in the * disable case. */ /* FALLTHROUGH */ case IIS_DISABLED: switch (event) { case RESTARTER_EVENT_TYPE_ENABLE: /* * The instance needs enabling. Commence reading its * configuration and if successful place the instance * in the offline state and let process_offline_inst() * take it from there. */ destroy_instance_cfg(instance->config); instance->config = read_instance_cfg(instance->fmri); if (instance->config != NULL) { update_state(instance, IIS_OFFLINE, RERR_RESTART); process_offline_inst(instance); } else { log_invalid_cfg(instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_RESTART); } break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance be * placed in the maintenance state, so just update its * state to maintenance. */ update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_MAINTENANCE: switch (event) { case RESTARTER_EVENT_TYPE_ADMIN_MAINT_OFF: case RESTARTER_EVENT_TYPE_ADMIN_DISABLE: /* * The master restarter has requested that the instance * be taken out of maintenance. Read its configuration, * and if successful place the instance in the offline * state and call process_offline_inst() to take it * from there. */ destroy_instance_cfg(instance->config); instance->config = read_instance_cfg(instance->fmri); if (instance->config != NULL) { update_state(instance, IIS_OFFLINE, RERR_RESTART); process_offline_inst(instance); } else { boolean_t enabled; /* * The configuration was invalid. If the * service has disabled requested, let's * just place the instance in disabled even * though we haven't been able to run its * disable method, as the slightly incorrect * state is likely to be less of an issue to * an administrator than refusing to move an * instance to disabled. If disable isn't * requested, re-mark the service's state * as maintenance, so the administrator can * see the request was processed. */ if ((read_enable_merged(instance->fmri, &enabled) == 0) && !enabled) { update_state(instance, IIS_DISABLED, RERR_RESTART); } else { log_invalid_cfg(instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); } } break; } break; case IIS_OFFLINE_CONRATE: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: /* * The instance wants disabling. Take the instance * offline as for the dependencies unmet event above, * and then from there run the disable method to do * the work to take the instance to the disabled state. */ cancel_inst_timer(instance); update_state(instance, IIS_OFFLINE, RERR_RESTART); (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance * be taken to maintenance. Cancel the timer setup * when we entered this state, and go directly to * maintenance. */ cancel_inst_timer(instance); update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; case IIS_OFFLINE_COPIES: switch (event) { case RESTARTER_EVENT_TYPE_DISABLE: /* * The instance wants disabling. Update the state * to offline, and run the disable method to do the * work to take it to the disabled state. */ update_state(instance, IIS_OFFLINE, RERR_RESTART); (void) run_method(instance, IM_DISABLE, NULL); break; case RESTARTER_EVENT_TYPE_ADMIN_MAINT_ON: case RESTARTER_EVENT_TYPE_DEPENDENCY_CYCLE: case RESTARTER_EVENT_TYPE_INVALID_DEPENDENCY: /* * The master restarter has requested the instance be * placed in maintenance. Since it's already offline * simply update the state. */ update_state(instance, IIS_MAINTENANCE, RERR_RESTART); break; } break; default: debug_msg("handle_restarter_event: instance in an " "unexpected state"); assert(0); } done: if (send_ack) ack_restarter_event(B_TRUE); } /* * Tries to read and process an event from the event pipe. If there isn't one * or an error occurred processing the event it returns -1. Else, if the event * is for an instance we're not already managing we read its state, add it to * our list to manage, and if appropriate read its configuration. Whether it's * new to us or not, we then handle the specific event. * Returns 0 if an event was read and processed successfully, else -1. */ static int process_restarter_event(void) { char *fmri; size_t fmri_size; restarter_event_type_t event_type; instance_t *instance; restarter_event_t *event; ssize_t sz; /* * Try to read an event pointer from the event pipe. */ errno = 0; switch (safe_read(rst_event_pipe[PE_CONSUMER], &event, sizeof (event))) { case 0: break; case 1: if (errno == EAGAIN) /* no event to read */ return (-1); /* other end of pipe closed */ /* FALLTHROUGH */ default: /* unexpected read error */ /* * There's something wrong with the event pipe. Let's * shutdown and be restarted. */ inetd_stop(); return (-1); } /* * Check if we're currently managing the instance which the event * pertains to. If not, read its complete state and add it to our * list to manage. */ fmri_size = scf_limit(SCF_LIMIT_MAX_FMRI_LENGTH); if ((fmri = malloc(fmri_size)) == NULL) { error_msg(strerror(errno)); goto fail; } sz = restarter_event_get_instance(event, fmri, fmri_size); if (sz >= fmri_size) assert(0); for (instance = uu_list_first(instance_list); instance != NULL; instance = uu_list_next(instance_list, instance)) { if (strcmp(instance->fmri, fmri) == 0) break; } if (instance == NULL) { int err; debug_msg("New instance to manage: %s", fmri); if (((instance = create_instance(fmri)) == NULL) || (retrieve_instance_state(instance) != 0) || (retrieve_method_pids(instance) != 0)) { destroy_instance(instance); free(fmri); goto fail; } if (((err = iterate_repository_contracts(instance, 0)) != 0) && (err != ENOENT)) { error_msg(gettext( "Failed to adopt contracts of instance %s: %s"), instance->fmri, strerror(err)); destroy_instance(instance); free(fmri); goto fail; } uu_list_node_init(instance, &instance->link, instance_pool); (void) uu_list_insert_after(instance_list, NULL, instance); /* * Only read configuration for instances that aren't in any of * the disabled, maintenance or uninitialized states, since * they'll read it on state exit. */ if ((instance->cur_istate != IIS_DISABLED) && (instance->cur_istate != IIS_MAINTENANCE) && (instance->cur_istate != IIS_UNINITIALIZED)) { instance->config = read_instance_cfg(instance->fmri); if (instance->config == NULL) { log_invalid_cfg(instance->fmri); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); } } } free(fmri); event_type = restarter_event_get_type(event); debug_msg("Event type: %d for instance: %s", event_type, instance->fmri); /* * If the instance is currently running a method, don't process the * event now, but attach it to the instance for processing when * the instance finishes its transition. */ if (INST_IN_TRANSITION(instance)) { debug_msg("storing event %d for instance %s", event_type, instance->fmri); instance->pending_rst_event = event_type; } else { handle_restarter_event(instance, event_type, B_TRUE); } return (0); fail: ack_restarter_event(B_FALSE); return (-1); } /* * Do the state machine processing associated with the termination of instance * 'inst''s start method for the 'proto_name' protocol if this parameter is not * NULL. */ void process_start_term(instance_t *inst, char *proto_name) { basic_cfg_t *cfg; inst->copies--; if ((inst->cur_istate == IIS_MAINTENANCE) || (inst->cur_istate == IIS_DISABLED)) { /* do any further processing/checks when we exit these states */ return; } cfg = inst->config->basic; if (cfg->iswait) { proto_info_t *pi; boolean_t listen; switch (inst->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: case IIS_IN_REFRESH_METHOD: /* * A wait type service's start method has exited. * Check if the method was fired off in this inetd's * lifetime, or a previous one; if the former, * re-commence listening on the service's behalf; if * the latter, mark the service offline and let bind * attempts commence. */ listen = B_FALSE; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { /* * If a bound fd exists, the method was fired * off during this inetd's lifetime. */ if (pi->listen_fd != -1) { listen = B_TRUE; if (proto_name == NULL || strcmp(pi->proto, proto_name) == 0) break; } } if (pi != NULL) { if (poll_bound_fds(inst, B_TRUE, proto_name) != 0) handle_bind_failure(inst); } else if (listen == B_FALSE) { update_state(inst, IIS_OFFLINE, RERR_RESTART); create_bound_fds(inst); } } } else { /* * Check if a nowait service should be brought back online * after exceeding its copies limit. */ if ((inst->cur_istate == IIS_OFFLINE_COPIES) && !copies_limit_exceeded(inst)) { update_state(inst, IIS_OFFLINE, RERR_NONE); process_offline_inst(inst); } } } /* * If the instance has a pending event process it and initiate the * acknowledgement. */ static void process_pending_rst_event(instance_t *inst) { if (inst->pending_rst_event != RESTARTER_EVENT_TYPE_INVALID) { restarter_event_type_t re; debug_msg("Injecting pending event %d for instance %s", inst->pending_rst_event, inst->fmri); re = inst->pending_rst_event; inst->pending_rst_event = RESTARTER_EVENT_TYPE_INVALID; handle_restarter_event(inst, re, B_TRUE); } } /* * Do the state machine processing associated with the termination * of the specified instance's non-start method with the specified status. * Once the processing of the termination is done, the function also picks up * any processing that was blocked on the method running. */ void process_non_start_term(instance_t *inst, int status) { boolean_t ran_online_method = B_FALSE; if (status == IMRET_FAILURE) { error_msg(gettext("The %s method of instance %s failed, " "transitioning to maintenance"), methods[states[inst->cur_istate].method_running].name, inst->fmri); if ((inst->cur_istate == IIS_IN_ONLINE_METHOD) || (inst->cur_istate == IIS_IN_REFRESH_METHOD)) destroy_bound_fds(inst); update_state(inst, IIS_MAINTENANCE, RERR_FAULT); inst->maintenance_req = B_FALSE; inst->conn_rate_exceeded = B_FALSE; if (inst->new_config != NULL) { destroy_instance_cfg(inst->new_config); inst->new_config = NULL; } if (!inetd_stopping) process_pending_rst_event(inst); return; } /* non-failure method return */ if (status != IMRET_SUCCESS) { /* * An instance method never returned a supported return code. * We'll assume this means the method succeeded for now whilst * non-GL-cognizant methods are used - eg. pkill. */ debug_msg("The %s method of instance %s returned " "non-compliant exit code: %d, assuming success", methods[states[inst->cur_istate].method_running].name, inst->fmri, status); } /* * Update the state from the in-transition state. */ switch (inst->cur_istate) { case IIS_IN_ONLINE_METHOD: ran_online_method = B_TRUE; /* FALLTHROUGH */ case IIS_IN_REFRESH_METHOD: /* * If we've exhausted the bind retries, flag that by setting * the instance's state to degraded. */ if (inst->bind_retries_exceeded) { update_state(inst, IIS_DEGRADED, RERR_NONE); break; } /* FALLTHROUGH */ default: update_state(inst, methods[states[inst->cur_istate].method_running].dst_state, RERR_NONE); } if (inst->cur_istate == IIS_OFFLINE) { if (inst->new_config != NULL) { /* * This instance was found during refresh to need * taking offline because its newly read configuration * was sufficiently different. Now we're offline, * activate this new configuration. */ destroy_instance_cfg(inst->config); inst->config = inst->new_config; inst->new_config = NULL; } /* continue/complete any transitions that are in progress */ process_offline_inst(inst); } else if (ran_online_method) { /* * We've just successfully executed the online method. We have * a set of bound network fds that were created before running * this method, so now we're online start listening for * connections on them. */ if (poll_bound_fds(inst, B_TRUE, NULL) != 0) handle_bind_failure(inst); } /* * If we're now out of transition (process_offline_inst() could have * fired off another method), carry out any jobs that were blocked by * us being in transition. */ if (!INST_IN_TRANSITION(inst)) { if (inetd_stopping) { if (!instance_stopped(inst)) { /* * inetd is stopping, and this instance hasn't * been stopped. Inject a stop event. */ handle_restarter_event(inst, RESTARTER_EVENT_TYPE_STOP, B_FALSE); } } else { process_pending_rst_event(inst); } } } /* * Check if configuration file specified is readable. If not return B_FALSE, * else return B_TRUE. */ static boolean_t can_read_file(const char *path) { int ret; int serrno; do { ret = access(path, R_OK); } while ((ret < 0) && (errno == EINTR)); if (ret < 0) { if (errno != ENOENT) { serrno = errno; error_msg(gettext("Failed to access configuration " "file %s for performing modification checks: %s"), path, strerror(errno)); errno = serrno; } return (B_FALSE); } return (B_TRUE); } /* * Check whether the configuration file has changed contents since inetd * was last started/refreshed, and if so, log a message indicating that * inetconv needs to be run. */ static void check_conf_file(void) { char *new_hash; char *old_hash = NULL; scf_error_t ret; const char *file; if (conf_file == NULL) { /* * No explicit config file specified, so see if one of the * default two are readable, checking the primary one first * followed by the secondary. */ if (can_read_file(PRIMARY_DEFAULT_CONF_FILE)) { file = PRIMARY_DEFAULT_CONF_FILE; } else if ((errno == ENOENT) && can_read_file(SECONDARY_DEFAULT_CONF_FILE)) { file = SECONDARY_DEFAULT_CONF_FILE; } else { return; } } else { file = conf_file; if (!can_read_file(file)) return; } if (calculate_hash(file, &new_hash) == 0) { ret = retrieve_inetd_hash(&old_hash); if (((ret == SCF_ERROR_NONE) && (strcmp(old_hash, new_hash) != 0))) { /* modified config file */ warn_msg(gettext( "Configuration file %s has been modified since " "inetconv was last run. \"inetconv -i %s\" must be " "run to apply any changes to the SMF"), file, file); } else if ((ret != SCF_ERROR_NOT_FOUND) && (ret != SCF_ERROR_NONE)) { /* No message if hash not yet computed */ error_msg(gettext("Failed to check whether " "configuration file %s has been modified: %s"), file, scf_strerror(ret)); } free(old_hash); free(new_hash); } else { error_msg(gettext("Failed to check whether configuration file " "%s has been modified: %s"), file, strerror(errno)); } } /* * Refresh all inetd's managed instances and check the configuration file * for any updates since inetconv was last run, logging a message if there * are. We call the SMF refresh function to refresh each instance so that * the refresh request goes through the framework, and thus results in the * running snapshot of each instance being updated from the configuration * snapshot. */ static void inetd_refresh(void) { instance_t *inst; refresh_debug_flag(); /* call libscf to send refresh requests for all managed instances */ for (inst = uu_list_first(instance_list); inst != NULL; inst = uu_list_next(instance_list, inst)) { if (smf_refresh_instance(inst->fmri) < 0) { error_msg(gettext("Failed to refresh instance %s: %s"), inst->fmri, scf_strerror(scf_error())); } } /* * Log a message if the configuration file has changed since inetconv * was last run. */ check_conf_file(); } /* * Initiate inetd's shutdown. */ static void inetd_stop(void) { instance_t *inst; /* Block handling signals for stop and refresh */ (void) sighold(SIGHUP); (void) sighold(SIGTERM); /* Indicate inetd is coming down */ inetd_stopping = B_TRUE; /* Stop polling on restarter events. */ clear_pollfd(rst_event_pipe[PE_CONSUMER]); /* Stop polling for any more stop/refresh requests. */ clear_pollfd(uds_fd); /* * Send a stop event to all currently unstopped instances that * aren't in transition. For those that are in transition, the * event will get sent when the transition completes. */ for (inst = uu_list_first(instance_list); inst != NULL; inst = uu_list_next(instance_list, inst)) { if (!instance_stopped(inst) && !INST_IN_TRANSITION(inst)) handle_restarter_event(inst, RESTARTER_EVENT_TYPE_STOP, B_FALSE); } } /* * Sets up the intra-inetd-process Unix Domain Socket. * Returns -1 on error, else 0. */ static int uds_init(void) { struct sockaddr_un addr; if ((uds_fd = socket(AF_UNIX, SOCK_STREAM, 0)) < 0) { error_msg("socket: %s", strerror(errno)); return (-1); } disable_blocking(uds_fd); (void) unlink(INETD_UDS_PATH); /* clean-up any stale files */ (void) memset(&addr, 0, sizeof (addr)); addr.sun_family = AF_UNIX; /* CONSTCOND */ assert(sizeof (INETD_UDS_PATH) <= sizeof (addr.sun_path)); (void) strlcpy(addr.sun_path, INETD_UDS_PATH, sizeof (addr.sun_path)); if (bind(uds_fd, (struct sockaddr *)(&addr), sizeof (addr)) < 0) { error_msg(gettext("Failed to bind socket to %s: %s"), INETD_UDS_PATH, strerror(errno)); (void) close(uds_fd); return (-1); } (void) listen(uds_fd, UDS_BACKLOG); if ((set_pollfd(uds_fd, POLLIN)) == -1) { (void) close(uds_fd); (void) unlink(INETD_UDS_PATH); return (-1); } return (0); } static void uds_fini(void) { if (uds_fd != -1) (void) close(uds_fd); (void) unlink(INETD_UDS_PATH); } /* * Handle an incoming request on the Unix Domain Socket. Returns -1 if there * was an error handling the event, else 0. */ static int process_uds_event(void) { uds_request_t req; int fd; struct sockaddr_un addr; socklen_t len = sizeof (addr); int ret; uint_t retries = 0; ucred_t *ucred = NULL; uid_t euid; do { fd = accept(uds_fd, (struct sockaddr *)&addr, &len); } while ((fd < 0) && (errno == EINTR)); if (fd < 0) { if (errno != EWOULDBLOCK) error_msg("accept failed: %s", strerror(errno)); return (-1); } if (getpeerucred(fd, &ucred) == -1) { error_msg("getpeerucred failed: %s", strerror(errno)); (void) close(fd); return (-1); } /* Check peer credentials before acting on the request */ euid = ucred_geteuid(ucred); ucred_free(ucred); if (euid != 0 && getuid() != euid) { debug_msg("peer euid %u != uid %u", (uint_t)euid, (uint_t)getuid()); (void) close(fd); return (-1); } for (retries = 0; retries < UDS_RECV_RETRIES; retries++) { if (((ret = safe_read(fd, &req, sizeof (req))) != 1) || (errno != EAGAIN)) break; (void) poll(NULL, 0, 100); /* 100ms pause */ } if (ret != 0) { error_msg(gettext("Failed read: %s"), strerror(errno)); (void) close(fd); return (-1); } switch (req) { case UR_REFRESH_INETD: /* flag the request for event_loop() to process */ refresh_inetd_requested = B_TRUE; (void) close(fd); break; case UR_STOP_INETD: inetd_stop(); break; default: error_msg("unexpected UDS request"); (void) close(fd); return (-1); } return (0); } /* * Perform checks for common exec string errors. We limit the checks to * whether the file exists, is a regular file, and has at least one execute * bit set. We leave the core security checks to exec() so as not to duplicate * and thus incur the associated drawbacks, but hope to catch the common * errors here. */ static boolean_t passes_basic_exec_checks(const char *instance, const char *method, const char *path) { struct stat sbuf; /* check the file exists */ while (stat(path, &sbuf) == -1) { if (errno != EINTR) { error_msg(gettext( "Can't stat the %s method of instance %s: %s"), method, instance, strerror(errno)); return (B_FALSE); } } /* * Check if the file is a regular file and has at least one execute * bit set. */ if ((sbuf.st_mode & S_IFMT) != S_IFREG) { error_msg(gettext( "The %s method of instance %s isn't a regular file"), method, instance); return (B_FALSE); } else if ((sbuf.st_mode & (S_IXUSR | S_IXGRP | S_IXOTH)) == 0) { error_msg(gettext("The %s method instance %s doesn't have " "any execute permissions set"), method, instance); return (B_FALSE); } return (B_TRUE); } static void exec_method(instance_t *instance, instance_method_t method, method_info_t *mi, struct method_context *mthd_ctxt, const proto_info_t *pi) { char **args; char **env; const char *errf; int serrno; sigset_t mtset; basic_cfg_t *cfg = instance->config->basic; if (method == IM_START) { /* * If wrappers checks fail, pretend the method was exec'd and * failed. */ if (!tcp_wrappers_ok(instance)) exit(IMRET_FAILURE); } /* * Revert the disposition of handled signals and ignored signals to * their defaults, unblocking any blocked ones as a side effect. */ (void) sigset(SIGHUP, SIG_DFL); (void) sigset(SIGTERM, SIG_DFL); (void) sigset(SIGINT, SIG_DFL); /* * Ensure that other signals are unblocked */ (void) sigemptyset(&mtset); (void) sigprocmask(SIG_SETMASK, &mtset, (sigset_t *)NULL); /* * Setup exec arguments. Do this before the fd setup below, so our * logging related file fd doesn't get taken over before we call * expand_address(). */ if ((method == IM_START) && (strcmp(mi->exec_args_we.we_wordv[0], "%A") == 0)) { args = expand_address(instance, pi); } else { args = mi->exec_args_we.we_wordv; } /* Generate audit trail for start operations */ if (method == IM_START) { adt_event_data_t *ae; struct sockaddr_storage ss; priv_set_t *privset; socklen_t sslen = sizeof (ss); if ((ae = adt_alloc_event(audit_handle, ADT_inetd_connect)) == NULL) { error_msg(gettext("Unable to allocate audit event for " "the %s method of instance %s"), methods[method].name, instance->fmri); exit(IMRET_FAILURE); } /* * The inetd_connect audit record consists of: * Service name * Execution path * Remote address and port * Local port * Process privileges */ ae->adt_inetd_connect.service_name = cfg->svc_name; ae->adt_inetd_connect.cmd = mi->exec_path; if (instance->remote_addr.ss_family == AF_INET) { struct in_addr *in = SS_SINADDR(instance->remote_addr); ae->adt_inetd_connect.ip_adr[0] = in->s_addr; ae->adt_inetd_connect.ip_type = ADT_IPv4; } else { uint32_t *addr6; int i; ae->adt_inetd_connect.ip_type = ADT_IPv6; addr6 = (uint32_t *)SS_SINADDR(instance->remote_addr); for (i = 0; i < 4; ++i) ae->adt_inetd_connect.ip_adr[i] = addr6[i]; } ae->adt_inetd_connect.ip_remote_port = ntohs(SS_PORT(instance->remote_addr)); if (getsockname(instance->conn_fd, (struct sockaddr *)&ss, &sslen) == 0) ae->adt_inetd_connect.ip_local_port = ntohs(SS_PORT(ss)); privset = mthd_ctxt->priv_set; if (privset == NULL) { privset = priv_allocset(); if (privset != NULL && getppriv(PRIV_EFFECTIVE, privset) != 0) { priv_freeset(privset); privset = NULL; } } ae->adt_inetd_connect.privileges = privset; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); if (privset != NULL && mthd_ctxt->priv_set == NULL) priv_freeset(privset); } /* * Set method context before the fd setup below so we can output an * error message if it fails. */ if ((errno = restarter_set_method_context(mthd_ctxt, &errf)) != 0) { const char *msg; if (errno == -1) { if (strcmp(errf, "core_set_process_path") == 0) { msg = gettext("Failed to set the corefile path " "for the %s method of instance %s"); } else if (strcmp(errf, "setproject") == 0) { msg = gettext("Failed to assign a resource " "control for the %s method of instance %s"); } else if (strcmp(errf, "pool_set_binding") == 0) { msg = gettext("Failed to bind the %s method of " "instance %s to a pool due to a system " "error"); } else { assert(0); abort(); } error_msg(msg, methods[method].name, instance->fmri); exit(IMRET_FAILURE); } if (errf != NULL && strcmp(errf, "pool_set_binding") == 0) { switch (errno) { case ENOENT: msg = gettext("Failed to find resource pool " "for the %s method of instance %s"); break; case EBADF: msg = gettext("Failed to bind the %s method of " "instance %s to a pool due to invalid " "configuration"); break; case EINVAL: msg = gettext("Failed to bind the %s method of " "instance %s to a pool due to invalid " "pool name"); break; default: assert(0); abort(); } exit(IMRET_FAILURE); } if (errf != NULL) { error_msg(gettext("Failed to set credentials for the " "%s method of instance %s (%s: %s)"), methods[method].name, instance->fmri, errf, strerror(errno)); exit(IMRET_FAILURE); } switch (errno) { case ENOMEM: msg = gettext("Failed to set credentials for the %s " "method of instance %s (out of memory)"); break; case ENOENT: msg = gettext("Failed to set credentials for the %s " "method of instance %s (no passwd or shadow " "entry for user)"); break; default: assert(0); abort(); } error_msg(msg, methods[method].name, instance->fmri); exit(IMRET_FAILURE); } /* let exec() free mthd_ctxt */ /* setup standard fds */ if (method == IM_START) { (void) dup2(instance->conn_fd, STDIN_FILENO); } else { (void) close(STDIN_FILENO); (void) open("/dev/null", O_RDONLY); } (void) dup2(STDIN_FILENO, STDOUT_FILENO); (void) dup2(STDIN_FILENO, STDERR_FILENO); closefrom(STDERR_FILENO + 1); method_preexec(); env = set_smf_env(mthd_ctxt, instance, methods[method].name); if (env != NULL) { do { (void) execve(mi->exec_path, args, env); } while (errno == EINTR); } serrno = errno; /* start up logging again to report the error */ msg_init(); errno = serrno; error_msg( gettext("Failed to exec %s method of instance %s: %s"), methods[method].name, instance->fmri, strerror(errno)); if ((method == IM_START) && (instance->config->basic->iswait)) { /* * We couldn't exec the start method for a wait type service. * Eat up data from the endpoint, so that hopefully the * service's fd won't wake poll up on the next time round * event_loop(). This behavior is carried over from the old * inetd, and it seems somewhat arbitrary that it isn't * also done in the case of fork failures; but I guess * it assumes an exec failure is less likely to be the result * of a resource shortage, and is thus not worth retrying. */ consume_wait_data(instance, 0); } exit(IMRET_FAILURE); } static restarter_error_t get_method_error_success(instance_method_t method) { switch (method) { case IM_OFFLINE: return (RERR_RESTART); case IM_ONLINE: return (RERR_RESTART); case IM_DISABLE: return (RERR_RESTART); case IM_REFRESH: return (RERR_REFRESH); case IM_START: return (RERR_RESTART); } (void) fprintf(stderr, gettext("Internal fatal error in inetd.\n")); abort(); /* NOTREACHED */ } static int smf_kill_process(instance_t *instance, int sig) { rep_val_t *rv; int ret = IMRET_SUCCESS; /* Carry out process assassination */ for (rv = uu_list_first(instance->start_pids); rv != NULL; rv = uu_list_next(instance->start_pids, rv)) { if ((kill((pid_t)rv->val, sig) != 0) && (errno != ESRCH)) { ret = IMRET_FAILURE; error_msg(gettext("Unable to kill " "start process (%ld) of instance %s: %s"), rv->val, instance->fmri, strerror(errno)); } } return (ret); } /* * Runs the specified method of the specified service instance. * If the method was never specified, we handle it the same as if the * method was called and returned success, carrying on any transition the * instance may be in the midst of. * If the method isn't executable in its specified profile or an error occurs * forking a process to run the method in the function returns -1. * If a method binary is successfully executed, the function switches the * instance's cur state to the method's associated 'run' state and the next * state to the methods associated next state. * Returns -1 if there's an error before forking, else 0. */ int run_method(instance_t *instance, instance_method_t method, const proto_info_t *start_info) { pid_t child_pid; method_info_t *mi; struct method_context *mthd_ctxt = NULL; int sig = 0; int ret; instance_cfg_t *cfg = instance->config; ctid_t cid; boolean_t trans_failure = B_TRUE; int serrno; /* * Don't bother updating the instance's state for the start method * as there isn't a separate start method state. */ if (method != IM_START) update_instance_states(instance, get_method_state(method), methods[method].dst_state, get_method_error_success(method)); if ((mi = cfg->methods[method]) == NULL) { /* * If the absent method is IM_OFFLINE, default action needs * to be taken to avoid lingering processes which can prevent * the upcoming rebinding from happening. */ if ((method == IM_OFFLINE) && instance->config->basic->iswait) { warn_msg(gettext("inetd_offline method for instance %s " "is unspecified. Taking default action: kill."), instance->fmri); (void) str2sig("TERM", &sig); ret = smf_kill_process(instance, sig); process_non_start_term(instance, ret); return (0); } else { process_non_start_term(instance, IMRET_SUCCESS); return (0); } } /* Handle special method tokens, not allowed on start */ if (method != IM_START) { if (restarter_is_null_method(mi->exec_path)) { /* :true means nothing should be done */ process_non_start_term(instance, IMRET_SUCCESS); return (0); } if ((sig = restarter_is_kill_method(mi->exec_path)) >= 0) { /* Carry out contract assassination */ ret = iterate_repository_contracts(instance, sig); /* ENOENT means we didn't find any contracts */ if (ret != 0 && ret != ENOENT) { error_msg(gettext("Failed to send signal %d " "to contracts of instance %s: %s"), sig, instance->fmri, strerror(ret)); goto prefork_failure; } else { process_non_start_term(instance, IMRET_SUCCESS); return (0); } } if ((sig = restarter_is_kill_proc_method(mi->exec_path)) >= 0) { ret = smf_kill_process(instance, sig); process_non_start_term(instance, ret); return (0); } } /* * Get the associated method context before the fork so we can * modify the instances state if things go wrong. */ if ((mthd_ctxt = read_method_context(instance->fmri, methods[method].name, mi->exec_path)) == NULL) goto prefork_failure; /* * Perform some basic checks before we fork to limit the possibility * of exec failures, so we can modify the instance state if necessary. */ if (!passes_basic_exec_checks(instance->fmri, methods[method].name, mi->exec_path)) { trans_failure = B_FALSE; goto prefork_failure; } if (contract_prefork(instance->fmri, method) == -1) goto prefork_failure; child_pid = fork(); serrno = errno; contract_postfork(); switch (child_pid) { case -1: error_msg(gettext( "Unable to fork %s method of instance %s: %s"), methods[method].name, instance->fmri, strerror(serrno)); if ((serrno != EAGAIN) && (serrno != ENOMEM)) trans_failure = B_FALSE; goto prefork_failure; case 0: /* child */ exec_method(instance, method, mi, mthd_ctxt, start_info); /* NOTREACHED */ default: /* parent */ restarter_free_method_context(mthd_ctxt); mthd_ctxt = NULL; if (get_latest_contract(&cid) < 0) cid = -1; /* * Register this method so its termination is noticed and * the state transition this method participates in is * continued. */ if (register_method(instance, child_pid, cid, method, start_info->proto) != 0) { /* * Since we will never find out about the termination * of this method, if it's a non-start method treat * is as a failure so we don't block restarter event * processing on it whilst it languishes in a method * running state. */ error_msg(gettext("Failed to monitor status of " "%s method of instance %s"), methods[method].name, instance->fmri); if (method != IM_START) process_non_start_term(instance, IMRET_FAILURE); } add_method_ids(instance, child_pid, cid, method); /* do tcp tracing for those nowait instances that request it */ if ((method == IM_START) && cfg->basic->do_tcp_trace && !cfg->basic->iswait) { char buf[INET6_ADDRSTRLEN]; syslog(LOG_NOTICE, "%s[%d] from %s %d", cfg->basic->svc_name, child_pid, inet_ntop_native(instance->remote_addr.ss_family, SS_SINADDR(instance->remote_addr), buf, sizeof (buf)), ntohs(SS_PORT(instance->remote_addr))); } } return (0); prefork_failure: if (mthd_ctxt != NULL) { restarter_free_method_context(mthd_ctxt); mthd_ctxt = NULL; } if (method == IM_START) { /* * Only place a start method in maintenance if we're sure * that the failure was non-transient. */ if (!trans_failure) { destroy_bound_fds(instance); update_state(instance, IIS_MAINTENANCE, RERR_FAULT); } } else { /* treat the failure as if the method ran and failed */ process_non_start_term(instance, IMRET_FAILURE); } return (-1); } static int pending_connections(instance_t *instance, proto_info_t *pi) { if (instance->config->basic->istlx) { tlx_info_t *tl = (tlx_info_t *)pi; return (uu_list_numnodes(tl->conn_ind_queue) != 0); } else { return (0); } } static int accept_connection(instance_t *instance, proto_info_t *pi) { int fd; socklen_t size; if (instance->config->basic->istlx) { tlx_info_t *tl = (tlx_info_t *)pi; tlx_pending_counter = \ tlx_pending_counter - uu_list_numnodes(tl->conn_ind_queue); fd = tlx_accept(instance->fmri, (tlx_info_t *)pi, &(instance->remote_addr)); tlx_pending_counter = \ tlx_pending_counter + uu_list_numnodes(tl->conn_ind_queue); } else { size = sizeof (instance->remote_addr); fd = accept(pi->listen_fd, (struct sockaddr *)&(instance->remote_addr), &size); if (fd < 0) error_msg("accept: %s", strerror(errno)); } return (fd); } /* * Handle an incoming connection request for a nowait service. * This involves accepting the incoming connection on a new fd. Connection * rate checks are then performed, transitioning the service to the * conrate offline state if these fail. Otherwise, the service's start method * is run (performing TCP wrappers checks if applicable as we do), and on * success concurrent copies checking is done, transitioning the service to the * copies offline state if this fails. */ static void process_nowait_request(instance_t *instance, proto_info_t *pi) { basic_cfg_t *cfg = instance->config->basic; int ret; adt_event_data_t *ae; char buf[BUFSIZ]; /* accept nowait service connections on a new fd */ if ((instance->conn_fd = accept_connection(instance, pi)) == -1) { /* * Failed accept. Return and allow the event loop to initiate * another attempt later if the request is still present. */ return; } /* * Limit connection rate of nowait services. If either conn_rate_max * or conn_rate_offline are <= 0, no connection rate limit checking * is done. If the configured rate is exceeded, the instance is taken * to the connrate_offline state and a timer scheduled to try and * bring the instance back online after the configured offline time. */ if ((cfg->conn_rate_max > 0) && (cfg->conn_rate_offline > 0)) { if (instance->conn_rate_count++ == 0) { instance->conn_rate_start = time(NULL); } else if (instance->conn_rate_count > cfg->conn_rate_max) { time_t now = time(NULL); if ((now - instance->conn_rate_start) > 1) { instance->conn_rate_start = now; instance->conn_rate_count = 1; } else { /* Generate audit record */ if ((ae = adt_alloc_event(audit_handle, ADT_inetd_ratelimit)) == NULL) { error_msg(gettext("Unable to allocate " "rate limit audit event")); } else { adt_inetd_ratelimit_t *rl = &ae->adt_inetd_ratelimit; /* * The inetd_ratelimit audit * record consists of: * Service name * Connection rate limit */ rl->service_name = cfg->svc_name; (void) snprintf(buf, sizeof (buf), "limit=%lld", cfg->conn_rate_max); rl->limit = buf; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); } error_msg(gettext( "Instance %s has exceeded its configured " "connection rate, additional connections " "will not be accepted for %d seconds"), instance->fmri, cfg->conn_rate_offline); close_net_fd(instance, instance->conn_fd); instance->conn_fd = -1; destroy_bound_fds(instance); instance->conn_rate_count = 0; instance->conn_rate_exceeded = B_TRUE; (void) run_method(instance, IM_OFFLINE, NULL); return; } } } ret = run_method(instance, IM_START, pi); close_net_fd(instance, instance->conn_fd); instance->conn_fd = -1; if (ret == -1) /* the method wasn't forked */ return; instance->copies++; /* * Limit concurrent connections of nowait services. */ if (copies_limit_exceeded(instance)) { /* Generate audit record */ if ((ae = adt_alloc_event(audit_handle, ADT_inetd_copylimit)) == NULL) { error_msg(gettext("Unable to allocate copy limit " "audit event")); } else { /* * The inetd_copylimit audit record consists of: * Service name * Copy limit */ ae->adt_inetd_copylimit.service_name = cfg->svc_name; (void) snprintf(buf, sizeof (buf), "limit=%lld", cfg->max_copies); ae->adt_inetd_copylimit.limit = buf; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); } warn_msg(gettext("Instance %s has reached its maximum " "configured copies, no new connections will be accepted"), instance->fmri); destroy_bound_fds(instance); (void) run_method(instance, IM_OFFLINE, NULL); } } /* * Handle an incoming request for a wait type service. * Failure rate checking is done first, taking the service to the maintenance * state if the checks fail. Following this, the service's start method is run, * and on success, we stop listening for new requests for this service. */ static void process_wait_request(instance_t *instance, const proto_info_t *pi) { basic_cfg_t *cfg = instance->config->basic; int ret; adt_event_data_t *ae; char buf[BUFSIZ]; instance->conn_fd = pi->listen_fd; /* * Detect broken servers and transition them to maintenance. If a * wait type service exits without accepting the connection or * consuming (reading) the datagram, that service's descriptor will * select readable again, and inetd will fork another instance of * the server. If either wait_fail_cnt or wait_fail_interval are <= 0, * no failure rate detection is done. */ if ((cfg->wait_fail_cnt > 0) && (cfg->wait_fail_interval > 0)) { if (instance->fail_rate_count++ == 0) { instance->fail_rate_start = time(NULL); } else if (instance->fail_rate_count > cfg->wait_fail_cnt) { time_t now = time(NULL); if ((now - instance->fail_rate_start) > cfg->wait_fail_interval) { instance->fail_rate_start = now; instance->fail_rate_count = 1; } else { /* Generate audit record */ if ((ae = adt_alloc_event(audit_handle, ADT_inetd_failrate)) == NULL) { error_msg(gettext("Unable to allocate " "failure rate audit event")); } else { adt_inetd_failrate_t *fr = &ae->adt_inetd_failrate; /* * The inetd_failrate audit record * consists of: * Service name * Failure rate * Interval * Last two are expressed as k=v pairs * in the values field. */ fr->service_name = cfg->svc_name; (void) snprintf(buf, sizeof (buf), "limit=%lld,interval=%d", cfg->wait_fail_cnt, cfg->wait_fail_interval); fr->values = buf; (void) adt_put_event(ae, ADT_SUCCESS, ADT_SUCCESS); adt_free_event(ae); } error_msg(gettext( "Instance %s has exceeded its configured " "failure rate, transitioning to " "maintenance"), instance->fmri); instance->fail_rate_count = 0; destroy_bound_fds(instance); instance->maintenance_req = B_TRUE; (void) run_method(instance, IM_OFFLINE, NULL); return; } } } ret = run_method(instance, IM_START, pi); instance->conn_fd = -1; if (ret == 0) { /* * Stop listening for connections now we've fired off the * server for a wait type instance. */ (void) poll_bound_fds(instance, B_FALSE, pi->proto); } } /* * Process any networks requests for each proto for each instance. */ void process_network_events(void) { instance_t *instance; for (instance = uu_list_first(instance_list); instance != NULL; instance = uu_list_next(instance_list, instance)) { basic_cfg_t *cfg; proto_info_t *pi; /* * Ignore instances in states that definitely don't have any * listening fds. */ switch (instance->cur_istate) { case IIS_ONLINE: case IIS_DEGRADED: case IIS_IN_REFRESH_METHOD: break; default: continue; } cfg = instance->config->basic; for (pi = uu_list_first(cfg->proto_list); pi != NULL; pi = uu_list_next(cfg->proto_list, pi)) { if (((pi->listen_fd != -1) && isset_pollfd(pi->listen_fd)) || pending_connections(instance, pi)) { if (cfg->iswait) { process_wait_request(instance, pi); } else { process_nowait_request(instance, pi); } } } } } /* ARGSUSED0 */ static void sigterm_handler(int sig) { got_sigterm = B_TRUE; } /* ARGSUSED0 */ static void sighup_handler(int sig) { refresh_inetd_requested = B_TRUE; } /* * inetd's major work loop. This function sits in poll waiting for events * to occur, processing them when they do. The possible events are * master restarter requests, expired timer queue timers, stop/refresh signal * requests, contract events indicating process termination, stop/refresh * requests originating from one of the stop/refresh inetd processes and * network events. * The loop is exited when a stop request is received and processed, and * all the instances have reached a suitable 'stopping' state. */ static void event_loop(void) { instance_t *instance; int timeout; for (;;) { int pret = -1; if (tlx_pending_counter != 0) timeout = 0; else timeout = iu_earliest_timer(timer_queue); if (!got_sigterm && !refresh_inetd_requested) { pret = poll(poll_fds, num_pollfds, timeout); if ((pret == -1) && (errno != EINTR)) { error_msg(gettext("poll failure: %s"), strerror(errno)); continue; } } if (got_sigterm) { msg_fini(); inetd_stop(); got_sigterm = B_FALSE; goto check_if_stopped; } /* * Process any stop/refresh requests from the Unix Domain * Socket. */ if ((pret != -1) && isset_pollfd(uds_fd)) { while (process_uds_event() == 0) ; } /* * Process refresh request. We do this check after the UDS * event check above, as it would be wasted processing if we * started refreshing inetd based on a SIGHUP, and then were * told to shut-down via a UDS event. */ if (refresh_inetd_requested) { refresh_inetd_requested = B_FALSE; if (!inetd_stopping) inetd_refresh(); } /* * We were interrupted by a signal. Don't waste any more * time processing a potentially inaccurate poll return. */ if (pret == -1) continue; /* * Process any instance restarter events. */ if (isset_pollfd(rst_event_pipe[PE_CONSUMER])) { while (process_restarter_event() == 0) ; } /* * Process any expired timers (bind retry, con-rate offline, * method timeouts). */ (void) iu_expire_timers(timer_queue); process_terminated_methods(); /* * If inetd is stopping, check whether all our managed * instances have been stopped and we can return. */ if (inetd_stopping) { check_if_stopped: for (instance = uu_list_first(instance_list); instance != NULL; instance = uu_list_next(instance_list, instance)) { if (!instance_stopped(instance)) { debug_msg("%s not yet stopped", instance->fmri); break; } } /* if all instances are stopped, return */ if (instance == NULL) return; } process_network_events(); } } static void fini(void) { method_fini(); uds_fini(); if (timer_queue != NULL) iu_tq_destroy(timer_queue); /* * We don't bother to undo the restarter interface at all. * Because of quirks in the interface, there is no way to * disconnect from the channel and cause any new events to be * queued. However, any events which are received and not * acknowledged will be re-sent when inetd restarts as long as inetd * uses the same subscriber ID, which it does. * * By keeping the event pipe open but ignoring it, any events which * occur will cause restarter_event_proxy to hang without breaking * anything. */ if (instance_list != NULL) { void *cookie = NULL; instance_t *inst; while ((inst = uu_list_teardown(instance_list, &cookie)) != NULL) destroy_instance(inst); uu_list_destroy(instance_list); } if (instance_pool != NULL) uu_list_pool_destroy(instance_pool); tlx_fini(); config_fini(); repval_fini(); poll_fini(); /* Close audit session */ (void) adt_end_session(audit_handle); } static int init(void) { int err; if (repval_init() < 0) goto failed; if (config_init() < 0) goto failed; refresh_debug_flag(); if (tlx_init() < 0) goto failed; /* Setup instance list. */ if ((instance_pool = uu_list_pool_create("instance_pool", sizeof (instance_t), offsetof(instance_t, link), NULL, UU_LIST_POOL_DEBUG)) == NULL) { error_msg("%s: %s", gettext("Failed to create instance pool"), uu_strerror(uu_error())); goto failed; } if ((instance_list = uu_list_create(instance_pool, NULL, 0)) == NULL) { error_msg("%s: %s", gettext("Failed to create instance list"), uu_strerror(uu_error())); goto failed; } /* * Create event pipe to communicate events with the main event * loop and add it to the event loop's fdset. */ if (pipe(rst_event_pipe) < 0) { error_msg("pipe: %s", strerror(errno)); goto failed; } /* * We only leave the producer end to block on reads/writes as we * can't afford to block in the main thread, yet need to in * the restarter event thread, so it can sit and wait for an * acknowledgement to be written to the pipe. */ disable_blocking(rst_event_pipe[PE_CONSUMER]); if ((set_pollfd(rst_event_pipe[PE_CONSUMER], POLLIN)) == -1) goto failed; /* * Register with master restarter for managed service events. This * will fail, amongst other reasons, if inetd is already running. */ if ((err = restarter_bind_handle(RESTARTER_EVENT_VERSION, INETD_INSTANCE_FMRI, restarter_event_proxy, 0, &rst_event_handle)) != 0) { error_msg(gettext( "Failed to register for restarter events: %s"), strerror(err)); goto failed; } if (contract_init() < 0) goto failed; if ((timer_queue = iu_tq_create()) == NULL) { error_msg(gettext("Failed to create timer queue.")); goto failed; } if (uds_init() < 0) goto failed; if (method_init() < 0) goto failed; /* Initialize auditing session */ if (adt_start_session(&audit_handle, NULL, ADT_USE_PROC_DATA) != 0) { error_msg(gettext("Unable to start audit session")); } /* * Initialize signal dispositions/masks */ (void) sigset(SIGHUP, sighup_handler); (void) sigset(SIGTERM, sigterm_handler); (void) sigignore(SIGINT); return (0); failed: fini(); return (-1); } static int start_method(void) { int i; int pipe_fds[2]; int child; /* Create pipe for child to notify parent of initialization success. */ if (pipe(pipe_fds) < 0) { error_msg("pipe: %s", strerror(errno)); return (SMF_EXIT_ERR_OTHER); } if ((child = fork()) == -1) { error_msg("fork: %s", strerror(errno)); (void) close(pipe_fds[PE_CONSUMER]); (void) close(pipe_fds[PE_PRODUCER]); return (SMF_EXIT_ERR_OTHER); } else if (child > 0) { /* parent */ /* Wait on child to return success of initialization. */ (void) close(pipe_fds[PE_PRODUCER]); if ((safe_read(pipe_fds[PE_CONSUMER], &i, sizeof (i)) != 0) || (i < 0)) { error_msg(gettext( "Initialization failed, unable to start")); (void) close(pipe_fds[PE_CONSUMER]); /* * Batch all initialization errors as 'other' errors, * resulting in retries being attempted. */ return (SMF_EXIT_ERR_OTHER); } else { (void) close(pipe_fds[PE_CONSUMER]); return (SMF_EXIT_OK); } } else { /* child */ /* * Perform initialization and return success code down * the pipe. */ (void) close(pipe_fds[PE_CONSUMER]); i = init(); if ((safe_write(pipe_fds[PE_PRODUCER], &i, sizeof (i)) < 0) || (i < 0)) { error_msg(gettext("pipe write failure: %s"), strerror(errno)); exit(1); } (void) close(pipe_fds[PE_PRODUCER]); (void) setsid(); /* * Log a message if the configuration file has changed since * inetconv was last run. */ check_conf_file(); event_loop(); fini(); debug_msg("inetd stopped"); msg_fini(); exit(0); } /* NOTREACHED */ } /* * When inetd is run from outside the SMF, this message is output to provide * the person invoking inetd with further information that will help them * understand how to start and stop inetd, and to achieve the other * behaviors achievable with the legacy inetd command line interface, if * it is possible. */ static void legacy_usage(void) { (void) fprintf(stderr, "inetd is now an smf(7) managed service and can no longer be run " "from the\n" "command line. To enable or disable inetd refer to svcadm(8) on\n" "how to enable \"%s\", the inetd instance.\n" "\n" "The traditional inetd command line option mappings are:\n" "\t-d : there is no supported debug output\n" "\t-s : inetd is only runnable from within the SMF\n" "\t-t : See inetadm(8) on how to enable TCP tracing\n" "\t-r : See inetadm(8) on how to set a failure rate\n" "\n" "To specify an alternative configuration file see svccfg(8)\n" "for how to modify the \"%s/%s\" string type property of\n" "the inetd instance, and modify it according to the syntax:\n" "\"%s [alt_config_file] %%m\".\n" "\n" "For further information on inetd see inetd(8).\n", INETD_INSTANCE_FMRI, START_METHOD_ARG, SCF_PROPERTY_EXEC, INETD_PATH); } /* * Usage message printed out for usage errors when running under the SMF. */ static void smf_usage(const char *arg0) { error_msg("Usage: %s [alt_conf_file] %s|%s|%s", arg0, START_METHOD_ARG, STOP_METHOD_ARG, REFRESH_METHOD_ARG); } /* * Returns B_TRUE if we're being run from within the SMF, else B_FALSE. */ static boolean_t run_through_smf(void) { char *fmri; /* * check if the instance fmri environment variable has been set by * our restarter. */ return (((fmri = getenv("SMF_FMRI")) != NULL) && (strcmp(fmri, INETD_INSTANCE_FMRI) == 0)); } int main(int argc, char *argv[]) { char *method; int ret; #if !defined(TEXT_DOMAIN) #define TEXT_DOMAIN "SYS_TEST" #endif (void) textdomain(TEXT_DOMAIN); (void) setlocale(LC_ALL, ""); if (!run_through_smf()) { legacy_usage(); return (SMF_EXIT_ERR_NOSMF); } msg_init(); /* setup logging */ (void) enable_extended_FILE_stdio(-1, -1); /* inetd invocation syntax is inetd [alt_conf_file] method_name */ switch (argc) { case 2: method = argv[1]; break; case 3: conf_file = argv[1]; method = argv[2]; break; default: smf_usage(argv[0]); return (SMF_EXIT_ERR_CONFIG); } if (strcmp(method, START_METHOD_ARG) == 0) { ret = start_method(); } else if (strcmp(method, STOP_METHOD_ARG) == 0) { ret = stop_method(); } else if (strcmp(method, REFRESH_METHOD_ARG) == 0) { ret = refresh_method(); } else { smf_usage(argv[0]); return (SMF_EXIT_ERR_CONFIG); } 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 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #ifndef _INETD_IMPL_H #define _INETD_IMPL_H /* * Header file containing inetd's shared types/data structures and * function declarations. */ #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #include #include #include #include /* * Number of consecutive retries of a repository operation that failed due * to a broken connection performed before giving up and failing. */ #define REP_OP_RETRIES 10 /* retryable SMF method error */ #define SMF_EXIT_ERR_OTHER 1 /* inetd's syslog ident string */ #define SYSLOG_IDENT "inetd" /* Is this instance currently executing a method ? */ #define INST_IN_TRANSITION(i) ((i)->next_istate != IIS_NONE) /* Names of properties that inetd uses to store instance state. */ #define PR_NAME_NON_START_PID "non_start_pid" #define PR_NAME_START_PIDS "start_pids" #define PR_NAME_CUR_INT_STATE "cur_state" #define PR_NAME_NEXT_INT_STATE "next_state" /* Name of the property group that holds debug flag */ #define PG_NAME_APPLICATION_CONFIG "config" /* Name of the property which holds the debug flag value */ #define PR_NAME_DEBUG_FLAG "debug" /* * Instance states used internal to svc.inetd. * NOTE: The states table in cmd/cmd-inetd/inetd/inetd.c relies on the * ordering of this enumeration, so take care if modifying it. */ typedef enum { IIS_UNINITIALIZED, IIS_ONLINE, IIS_IN_ONLINE_METHOD, IIS_OFFLINE, IIS_IN_OFFLINE_METHOD, IIS_DISABLED, IIS_IN_DISABLE_METHOD, IIS_IN_REFRESH_METHOD, IIS_MAINTENANCE, IIS_OFFLINE_CONRATE, IIS_OFFLINE_BIND, IIS_OFFLINE_COPIES, IIS_DEGRADED, IIS_NONE } internal_inst_state_t; /* * inetd's instance methods. * NOTE: The methods table in cmd/cmd-inetd/inetd/util.c relies on the * ordering of this enumeration, so take care if modifying it. */ typedef enum { IM_START, IM_ONLINE, IM_OFFLINE, IM_DISABLE, IM_REFRESH, NUM_METHODS, IM_NONE } instance_method_t; /* Collection of information pertaining to a method */ typedef struct { char *exec_path; /* path passed to exec() */ /* * Structure returned from wordexp(3c) that contains an expansion of the * exec property into a form suitable for exec(2). */ wordexp_t exec_args_we; /* * Copy of the first argument of the above wordexp_t structure in the * event that an alternate arg0 is provided, and we replace the first * argument with the alternate arg0. This is necessary so the * contents of the wordexp_t structure can be returned to their * original form as returned from wordexp(3c), which is a requirement * for calling wordfree(3c), wordexp()'s associated cleanup routine. */ const char *wordexp_arg0_backup; /* time a method can run for before being considered broken */ int timeout; } method_info_t; typedef struct { basic_cfg_t *basic; method_info_t *methods[NUM_METHODS]; } instance_cfg_t; /* * Structure used to construct a list of int64_t's and their associated * scf values. Used to store lists of process ids, internal states, and to * store the associated scf value used when writing the values back to the * repository. */ typedef struct { int64_t val; scf_value_t *scf_val; uu_list_node_t link; } rep_val_t; /* Structure containing the state and configuration of a service instance. */ typedef struct { char *fmri; /* fd we're going to take a connection on */ int conn_fd; /* number of copies of this instance active */ int64_t copies; /* connection rate counters */ int64_t conn_rate_count; time_t conn_rate_start; /* failure rate counters */ int64_t fail_rate_count; time_t fail_rate_start; /* bind failure count */ int64_t bind_fail_count; /* pids of currently running methods */ uu_list_t *non_start_pid; uu_list_t *start_pids; /* ctids of currently running start methods */ uu_list_t *start_ctids; /* remote address, used for TCP tracing */ struct sockaddr_storage remote_addr; internal_inst_state_t cur_istate; internal_inst_state_t next_istate; /* repository compatible versions of the above 2 states */ uu_list_t *cur_istate_rep; uu_list_t *next_istate_rep; /* * Current instance configuration resulting from its repository * configuration. */ instance_cfg_t *config; /* * Soon to be applied instance configuration. This configuration was * read during a refresh when this instance was online, and the * instance needed taking offline for this configuration to be applied. * The instance is currently on its way offline, and this configuration * will become the current configuration when it arrives there. */ instance_cfg_t *new_config; /* current pending conrate-offline/method timer; -1 if none pending */ iu_timer_id_t timer_id; /* current pending bind retry timer; -1 if none pending */ iu_timer_id_t bind_timer_id; /* * Flags that assist in the fanout of an instance arriving in the * offline state on-route to some other state. */ boolean_t disable_req; boolean_t maintenance_req; boolean_t conn_rate_exceeded; boolean_t bind_retries_exceeded; /* * Event waiting to be processed. RESTARTER_EVENT_TYPE_INVALID is used * to mean no event waiting. */ restarter_event_type_t pending_rst_event; /* link to next instance in list */ uu_list_node_t link; } instance_t; /* Structure used to store information pertaining to instance method types. */ typedef struct { instance_method_t method; const char *name; internal_inst_state_t dst_state; } method_type_info_t; extern uu_list_t *instance_list; extern struct pollfd *poll_fds; extern nfds_t num_pollfds; extern method_type_info_t methods[]; extern iu_tq_t *timer_queue; extern uu_list_pool_t *conn_ind_pool; extern boolean_t debug_enabled; /* * util.c */ extern void msg_init(void); extern void msg_fini(void); /* PRINTFLIKE1 */ extern void debug_msg(const char *, ...); /* PRINTFLIKE1 */ extern void error_msg(const char *, ...); /* PRINTFLIKE1 */ extern void warn_msg(const char *, ...); extern void poll_fini(void); extern boolean_t isset_pollfd(int); extern void clear_pollfd(int); extern int set_pollfd(int, uint16_t); extern struct pollfd *find_pollfd(int); extern int safe_read(int, void *, size_t); extern boolean_t copies_limit_exceeded(instance_t *); extern void cancel_inst_timer(instance_t *); extern void cancel_bind_timer(instance_t *); extern void enable_blocking(int); extern void disable_blocking(int); /* * tlx.c */ extern rpc_info_t *create_rpc_info(const char *, const char *, const char *, int, int); extern void destroy_rpc_info(rpc_info_t *); extern boolean_t rpc_info_equal(const rpc_info_t *, const rpc_info_t *); extern int register_rpc_service(const char *, const rpc_info_t *); extern void unregister_rpc_service(const char *, const rpc_info_t *); extern int create_bound_endpoint(const instance_t *, tlx_info_t *); extern void close_net_fd(instance_t *, int); extern int tlx_accept(const char *, tlx_info_t *, struct sockaddr_storage *); extern struct t_call *dequeue_conind(uu_list_t *); extern int queue_conind(uu_list_t *, struct t_call *); extern void tlx_fini(void); extern int tlx_init(void); extern boolean_t tlx_info_equal(const tlx_info_t *, const tlx_info_t *, boolean_t); extern void consume_wait_data(instance_t *, int); /* * config.c */ extern int config_init(void); extern void config_fini(void); extern boolean_t socket_info_equal(const socket_info_t *, const socket_info_t *, boolean_t); extern boolean_t method_info_equal(const method_info_t *, const method_info_t *); extern struct method_context *read_method_context(const char *, const char *, const char *); extern void destroy_instance_cfg(instance_cfg_t *); extern instance_cfg_t *read_instance_cfg(const char *); extern boolean_t bind_config_equal(const basic_cfg_t *, const basic_cfg_t *); extern int read_enable_merged(const char *, boolean_t *); extern void refresh_debug_flag(void); /* * repval.c */ extern void repval_fini(void); extern int repval_init(void); extern uu_list_t *create_rep_val_list(void); extern void destroy_rep_val_list(uu_list_t *); extern scf_error_t store_rep_vals(uu_list_t *, const char *, const char *); extern scf_error_t retrieve_rep_vals(uu_list_t *, const char *, const char *); extern rep_val_t *find_rep_val(uu_list_t *, int64_t); extern int set_single_rep_val(uu_list_t *, int64_t); extern int64_t get_single_rep_val(uu_list_t *); extern int add_rep_val(uu_list_t *, int64_t); extern void remove_rep_val(uu_list_t *, int64_t); extern void empty_rep_val_list(uu_list_t *); extern int make_handle_bound(scf_handle_t *); extern int add_remove_contract(instance_t *, boolean_t, ctid_t); extern int iterate_repository_contracts(instance_t *, int); /* * contracts.c */ extern int contract_init(void); extern void contract_fini(void); void contract_postfork(void); int contract_prefork(const char *, int); extern int get_latest_contract(ctid_t *cid); extern int adopt_contract(ctid_t, const char *); extern int abandon_contract(ctid_t); /* * inetd.c */ extern void process_offline_inst(instance_t *); extern void process_non_start_term(instance_t *, int); extern void process_start_term(instance_t *, char *); extern void remove_method_ids(instance_t *, pid_t, ctid_t, instance_method_t); /* * env.c */ char **set_smf_env(struct method_context *, instance_t *, const char *); /* * wait.c */ extern int register_method(instance_t *, pid_t, ctid_t cid, instance_method_t, char *); extern int method_init(void); extern void method_fini(void); extern void process_terminated_methods(void); extern void unregister_instance_methods(const instance_t *); extern void method_preexec(void); #ifdef __cplusplus } #endif #endif /* _INETD_IMPL_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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * This file contains routines to manipulate lists of repository values that * are used to store process ids and the internal state. There are routines * to read/write the lists from/to the repository and routines to modify or * inspect the lists. It also contains routines that deal with the * repository side of contract ids. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* * Number of consecutive repository bind retries performed by bind_to_rep() * before failing. */ #define BIND_TO_REP_RETRIES 10 /* Name of property group where inetd's state for a service is stored. */ #define PG_NAME_INSTANCE_STATE (const char *) "inetd_state" /* uu_list repval list pool */ static uu_list_pool_t *rep_val_pool = NULL; /* * Repository object pointers that get set-up in repval_init() and closed down * in repval_fini(). They're used in _retrieve_rep_vals(), _store_rep_vals(), * add_remove_contract_norebind(), and adopt_repository_contracts(). They're * global so they can be initialized once on inetd startup, and re-used * there-after in the referenced functions. */ static scf_handle_t *rep_handle = NULL; static scf_propertygroup_t *pg = NULL; static scf_instance_t *inst = NULL; static scf_transaction_t *trans = NULL; static scf_transaction_entry_t *entry = NULL; static scf_property_t *prop = NULL; /* * Pathname storage for paths generated from the fmri. * Used when updating the ctid and (start) pid files for an inetd service. */ static char genfmri_filename[MAXPATHLEN] = ""; static char genfmri_temp_filename[MAXPATHLEN] = ""; /* * Try and make the given handle bind be bound to the repository. If * it's already bound, or we succeed a new bind return 0; else return * -1 on failure, with the SCF error set to one of the following: * SCF_ERROR_NO_SERVER * SCF_ERROR_NO_RESOURCES */ int make_handle_bound(scf_handle_t *hdl) { uint_t retries; for (retries = 0; retries <= BIND_TO_REP_RETRIES; retries++) { if ((scf_handle_bind(hdl) == 0) || (scf_error() == SCF_ERROR_IN_USE)) return (0); assert(scf_error() != SCF_ERROR_INVALID_ARGUMENT); } return (-1); } int repval_init(void) { /* * Create the repval list pool. */ rep_val_pool = uu_list_pool_create("rep_val_pool", sizeof (rep_val_t), offsetof(rep_val_t, link), NULL, UU_LIST_POOL_DEBUG); if (rep_val_pool == NULL) { error_msg("%s: %s", gettext("Failed to create rep_val pool"), uu_strerror(uu_error())); return (-1); } /* * Create and bind a repository handle, and create all repository * objects that we'll use later that are associated with it. On any * errors we simply return -1 and let repval_fini() clean-up after * us. */ if ((rep_handle = scf_handle_create(SCF_VERSION)) == NULL) { error_msg("%s: %s", gettext("Failed to create repository handle"), scf_strerror(scf_error())); goto cleanup; } else if (make_handle_bound(rep_handle) == -1) { goto cleanup; } else if (((pg = scf_pg_create(rep_handle)) == NULL) || ((inst = scf_instance_create(rep_handle)) == NULL) || ((trans = scf_transaction_create(rep_handle)) == NULL) || ((entry = scf_entry_create(rep_handle)) == NULL) || ((prop = scf_property_create(rep_handle)) == NULL)) { error_msg("%s: %s", gettext("Failed to create repository object"), scf_strerror(scf_error())); goto cleanup; } return (0); cleanup: repval_fini(); return (-1); } void repval_fini(void) { if (rep_handle != NULL) { /* * We unbind from the repository before we free the repository * objects for efficiency reasons. */ (void) scf_handle_unbind(rep_handle); scf_pg_destroy(pg); pg = NULL; scf_instance_destroy(inst); inst = NULL; scf_transaction_destroy(trans); trans = NULL; scf_entry_destroy(entry); entry = NULL; scf_property_destroy(prop); prop = NULL; scf_handle_destroy(rep_handle); rep_handle = NULL; } if (rep_val_pool != NULL) { uu_list_pool_destroy(rep_val_pool); rep_val_pool = NULL; } } uu_list_t * create_rep_val_list(void) { uu_list_t *ret; if ((ret = uu_list_create(rep_val_pool, NULL, 0)) == NULL) assert(uu_error() == UU_ERROR_NO_MEMORY); return (ret); } void destroy_rep_val_list(uu_list_t *list) { if (list != NULL) { empty_rep_val_list(list); uu_list_destroy(list); } } rep_val_t * find_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv; for (rv = uu_list_first(list); rv != NULL; rv = uu_list_next(list, rv)) { if (rv->val == val) break; } return (rv); } int add_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv; if ((rv = malloc(sizeof (rep_val_t))) == NULL) return (-1); uu_list_node_init(rv, &rv->link, rep_val_pool); rv->val = val; rv->scf_val = NULL; (void) uu_list_insert_after(list, NULL, rv); return (0); } void remove_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv; if ((rv = find_rep_val(list, val)) != NULL) { uu_list_remove(list, rv); assert(rv->scf_val == NULL); free(rv); } } void empty_rep_val_list(uu_list_t *list) { void *cookie = NULL; rep_val_t *rv; while ((rv = uu_list_teardown(list, &cookie)) != NULL) { if (rv->scf_val != NULL) scf_value_destroy(rv->scf_val); free(rv); } } int64_t get_single_rep_val(uu_list_t *list) { rep_val_t *rv = uu_list_first(list); assert(rv != NULL); return (rv->val); } int set_single_rep_val(uu_list_t *list, int64_t val) { rep_val_t *rv = uu_list_first(list); if (rv == NULL) { if (add_rep_val(list, val) == -1) return (-1); } else { rv->val = val; } return (0); } /* * Partner to add_tr_entry_values. This function frees the scf_values created * in add_tr_entry_values() in the list 'vals'. */ static void remove_tr_entry_values(uu_list_t *vals) { rep_val_t *rval; for (rval = uu_list_first(vals); rval != NULL; rval = uu_list_next(vals, rval)) { if (rval->scf_val != NULL) { scf_value_destroy(rval->scf_val); rval->scf_val = NULL; } } } /* * This function creates and associates with transaction entry 'entry' an * scf value for each value in 'vals'. The pointers to the scf values * are stored in the list for later cleanup by remove_tr_entry_values. * Returns 0 on success, else -1 on error with scf_error() set to: * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. */ static int add_tr_entry_values(scf_handle_t *hdl, scf_transaction_entry_t *entry, uu_list_t *vals) { rep_val_t *rval; for (rval = uu_list_first(vals); rval != NULL; rval = uu_list_next(vals, rval)) { assert(rval->scf_val == NULL); if ((rval->scf_val = scf_value_create(hdl)) == NULL) { remove_tr_entry_values(vals); return (-1); } scf_value_set_integer(rval->scf_val, rval->val); if (scf_entry_add_value(entry, rval->scf_val) < 0) { remove_tr_entry_values(vals); return (-1); } } return (0); } /* * Stores the values contained in the list 'vals' into the property 'prop_name' * of the instance with fmri 'inst_fmri', within the instance's instance * state property group. * * Returns 0 on success, else one of the following on failure: * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_NO_RESOURCES if the server doesn't have required resources. * SCF_ERROR_VERSION_MISMATCH if program compiled against a newer libscf * than on system. * SCF_ERROR_PERMISSION_DENIED if insufficient privileges to modify pg. * SCF_ERROR_BACKEND_ACCESS if the repository back-end refused the pg modify. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. */ static scf_error_t _store_rep_vals(uu_list_t *vals, const char *inst_fmri, const char *prop_name) { int cret; int ret; if (scf_handle_decode_fmri(rep_handle, inst_fmri, NULL, NULL, inst, NULL, NULL, SCF_DECODE_FMRI_EXACT) == -1) return (scf_error()); /* * Fetch the instance state pg, and if it doesn't exist try and * create it. */ if (scf_instance_get_pg(inst, PG_NAME_INSTANCE_STATE, pg) < 0) { if (scf_error() != SCF_ERROR_NOT_FOUND) return (scf_error()); if (scf_instance_add_pg(inst, PG_NAME_INSTANCE_STATE, SCF_GROUP_FRAMEWORK, SCF_PG_FLAG_NONPERSISTENT, pg) < 0) return (scf_error()); } /* * Perform a transaction to write the values to the requested property. * If someone got there before us, loop and retry. */ do { if (scf_transaction_start(trans, pg) < 0) return (scf_error()); if ((scf_transaction_property_new(trans, entry, prop_name, SCF_TYPE_INTEGER) < 0) && (scf_transaction_property_change_type(trans, entry, prop_name, SCF_TYPE_INTEGER) < 0)) { ret = scf_error(); goto cleanup; } if (add_tr_entry_values(rep_handle, entry, vals) < 0) { ret = scf_error(); goto cleanup; } if ((cret = scf_transaction_commit(trans)) < 0) { ret = scf_error(); goto cleanup; } else if (cret == 0) { scf_transaction_reset(trans); scf_entry_reset(entry); remove_tr_entry_values(vals); if (scf_pg_update(pg) < 0) { ret = scf_error(); goto cleanup; } } } while (cret == 0); ret = 0; cleanup: scf_transaction_reset(trans); scf_entry_reset(entry); remove_tr_entry_values(vals); return (ret); } /* * Retrieves the repository values of property 'prop_name', of the instance * with fmri 'fmri', from within the instance's instance state property * group and adds them to the value list 'list'. * * Returns 0 on success, else one of the following values on error: * SCF_ERROR_NOT_FOUND if the property doesn't exist. * SCF_ERROR_NO_MEMORY if memory allocation failed. * SCF_ERROR_CONNECTION_BROKEN if the connection to the repository was broken. * SCF_ERROR_TYPE_MISMATCH if the property was of an unexpected type. * */ static scf_error_t _retrieve_rep_vals(uu_list_t *list, const char *fmri, const char *prop_name) { scf_simple_prop_t *sp; int64_t *ip; if ((sp = scf_simple_prop_get(rep_handle, fmri, PG_NAME_INSTANCE_STATE, prop_name)) == NULL) return (scf_error()); while ((ip = scf_simple_prop_next_integer(sp)) != NULL) { if (add_rep_val(list, *ip) == -1) { empty_rep_val_list(list); scf_simple_prop_free(sp); return (SCF_ERROR_NO_MEMORY); } } if (scf_error() != SCF_ERROR_NONE) { assert(scf_error() == SCF_ERROR_TYPE_MISMATCH); empty_rep_val_list(list); scf_simple_prop_free(sp); return (scf_error()); } scf_simple_prop_free(sp); return (0); } /* * Writes the repository values in the vals list to * a file that is generated based on the passed in fmri and name. * Returns 0 on success, * ENAMETOOLONG if unable to generate filename from fmri (including * the inability to create the directory for the generated filename) and * ENOENT on all other failures. */ static int repvals_to_file(const char *fmri, const char *name, uu_list_t *vals) { int tfd; FILE *tfp; /* temp fp */ rep_val_t *spval; /* Contains a start_pid or ctid */ int ret = 0; if (gen_filenms_from_fmri(fmri, name, genfmri_filename, genfmri_temp_filename) != 0) { /* Failure either from fmri too long or mkdir failure */ return (ENAMETOOLONG); } if ((tfd = mkstemp(genfmri_temp_filename)) == -1) { return (ENOENT); } if (fchmod(tfd, (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH)) == -1) { (void) close(tfd); ret = ENOENT; goto unlink_out; } if ((tfp = fdopen(tfd, "w")) == NULL) { (void) close(tfd); ret = ENOENT; goto unlink_out; } for (spval = uu_list_first(vals); spval != NULL; spval = uu_list_next(vals, spval)) { if (fprintf(tfp, "%lld\n", spval->val) <= 0) { (void) fclose(tfp); ret = ENOENT; goto unlink_out; } } if (fclose(tfp) != 0) { ret = ENOENT; goto unlink_out; } if (rename(genfmri_temp_filename, genfmri_filename) != 0) { ret = ENOENT; goto unlink_out; } return (0); unlink_out: if (unlink(genfmri_temp_filename) != 0) { warn_msg(gettext("Removal of temp file " "%s failed. Please remove manually."), genfmri_temp_filename); } return (ret); } /* * A routine that loops trying to read/write values until either success, * an error other than a broken repository connection or * the number of retries reaches REP_OP_RETRIES. * This action is used to read/write the values: * reads/writes to a file for the START_PIDS property due to scalability * problems with libscf * reads/writes to the repository for all other properties. * Returns 0 on success, else the error value from either _store_rep_vals or * _retrieve_rep_vals (based on whether 'store' was set or not), or one of the * following: * SCF_ERROR_NO_RESOURCES if the server doesn't have adequate resources * SCF_ERROR_NO_MEMORY if a memory allocation failure * SCF_ERROR_NO_SERVER if the server isn't running. * SCF_ERROR_CONSTRAINT_VIOLATED if an error in dealing with the speedy files */ static scf_error_t store_retrieve_rep_vals(uu_list_t *vals, const char *fmri, const char *prop, boolean_t store) { scf_error_t ret = 0; uint_t retries; FILE *tfp; /* temp fp */ int64_t tval; /* temp val holder */ int fscanf_ret; int fopen_retry_cnt = 2; /* inetd specific action for START_PIDS property */ if (strcmp(prop, PR_NAME_START_PIDS) == 0) { /* * Storage performance of START_PIDS is important, * so each instance has its own file and all start_pids * in the list are written to a temp file and then * moved (renamed). */ if (store) { /* Write all values in list to file */ if (repvals_to_file(fmri, "pid", vals)) { return (SCF_ERROR_CONSTRAINT_VIOLATED); } } else { /* no temp name needed */ if (gen_filenms_from_fmri(fmri, "pid", genfmri_filename, NULL) != 0) return (SCF_ERROR_CONSTRAINT_VIOLATED); retry_fopen: /* It's ok if no file, there are just no pids */ if ((tfp = fopen(genfmri_filename, "r")) == NULL) { if ((errno == EINTR) && (fopen_retry_cnt > 0)) { fopen_retry_cnt--; goto retry_fopen; } return (0); } /* fscanf may not set errno, so clear it first */ errno = 0; while ((fscanf_ret = fscanf(tfp, "%lld", &tval)) == 1) { /* If tval isn't a valid pid, then fail. */ if ((tval > MAXPID) || (tval <= 0)) { empty_rep_val_list(vals); return (SCF_ERROR_CONSTRAINT_VIOLATED); } if (add_rep_val(vals, tval) == -1) { empty_rep_val_list(vals); return (SCF_ERROR_NO_MEMORY); } errno = 0; } /* EOF is ok when no errno */ if ((fscanf_ret != EOF) || (errno != 0)) { empty_rep_val_list(vals); return (SCF_ERROR_CONSTRAINT_VIOLATED); } if (fclose(tfp) != 0) { /* for close failure just log a message */ warn_msg(gettext("Close of file %s failed."), genfmri_filename); } } } else { for (retries = 0; retries <= REP_OP_RETRIES; retries++) { if (make_handle_bound(rep_handle) == -1) { ret = scf_error(); break; } if ((ret = (store ? _store_rep_vals(vals, fmri, prop) : _retrieve_rep_vals(vals, fmri, prop))) != SCF_ERROR_CONNECTION_BROKEN) break; (void) scf_handle_unbind(rep_handle); } } return (ret); } scf_error_t store_rep_vals(uu_list_t *vals, const char *fmri, const char *prop) { return (store_retrieve_rep_vals(vals, fmri, prop, B_TRUE)); } scf_error_t retrieve_rep_vals(uu_list_t *vals, const char *fmri, const char *prop) { return (store_retrieve_rep_vals(vals, fmri, prop, B_FALSE)); } /* * Adds/removes a contract id to/from the cached list kept in the instance. * Then the cached list is written to a file named "ctid" in a directory * based on the fmri. Cached list is written to a file due to scalability * problems in libscf. The file "ctid" is used when inetd is restarted * so that inetd can adopt the contracts that it had previously. * Returns: * 0 on success * ENAMETOOLONG if unable to generate filename from fmri (including * the inability to create the directory for the generated filename) * ENOENT - failure accessing file * ENOMEM - memory allocation failure */ int add_remove_contract(instance_t *inst, boolean_t add, ctid_t ctid) { FILE *tfp; /* temp fp */ int ret = 0; int repval_ret = 0; int fopen_retry_cnt = 2; /* * Storage performance of contract ids is important, * so each instance has its own file. An add of a * ctid will be appended to the ctid file. * The removal of a ctid will result in the remaining * ctids in the list being written to a temp file and then * moved (renamed). */ if (add) { if (gen_filenms_from_fmri(inst->fmri, "ctid", genfmri_filename, NULL) != 0) { /* Failure either from fmri too long or mkdir failure */ return (ENAMETOOLONG); } retry_fopen: if ((tfp = fopen(genfmri_filename, "a")) == NULL) { if ((errno == EINTR) && (fopen_retry_cnt > 0)) { fopen_retry_cnt--; goto retry_fopen; } ret = ENOENT; goto out; } /* Always store ctids as long long */ if (fprintf(tfp, "%llu\n", (uint64_t)ctid) <= 0) { (void) fclose(tfp); ret = ENOENT; goto out; } if (fclose(tfp) != 0) { ret = ENOENT; goto out; } if (add_rep_val(inst->start_ctids, ctid) != 0) { ret = ENOMEM; goto out; } } else { remove_rep_val(inst->start_ctids, ctid); /* Write all values in list to file */ if ((repval_ret = repvals_to_file(inst->fmri, "ctid", inst->start_ctids)) != 0) { ret = repval_ret; goto out; } } out: return (ret); } /* * If sig !=0, iterate over all contracts in the cached list of contract * ids kept in the instance. Send each contract the specified signal. * If sig == 0, read in the contract ids that were last associated * with this instance (reload the cache) and call adopt_contract() * to take ownership. * * Returns 0 on success; * ENAMETOOLONG if unable to generate filename from fmri (including * the inability to create the directory for the generated filename) and * ENXIO if a failure accessing the file * ENOMEM if there was a memory allocation failure * ENOENT if the instance, its restarter property group, or its * contract property don't exist * EIO if invalid data read from the file */ int iterate_repository_contracts(instance_t *inst, int sig) { int ret = 0; FILE *fp; rep_val_t *spval = NULL; /* Contains a start_pid */ uint64_t tval; /* temp val holder */ uu_list_t *uup = NULL; int fscanf_ret; int fopen_retry_cnt = 2; if (sig != 0) { /* * Send a signal to all in the contract; ESRCH just * means they all exited before we could kill them */ for (spval = uu_list_first(inst->start_ctids); spval != NULL; spval = uu_list_next(inst->start_ctids, spval)) { if (sigsend(P_CTID, (ctid_t)spval->val, sig) == -1 && errno != ESRCH) { warn_msg(gettext("Unable to signal all " "contract members of instance %s: %s"), inst->fmri, strerror(errno)); } } return (0); } /* * sig == 0 case. * Attempt to adopt the contract for each ctid. */ if (gen_filenms_from_fmri(inst->fmri, "ctid", genfmri_filename, NULL) != 0) { /* Failure either from fmri too long or mkdir failure */ return (ENAMETOOLONG); } retry_fopen: /* It's ok if no file, there are no ctids to adopt */ if ((fp = fopen(genfmri_filename, "r")) == NULL) { if ((errno == EINTR) && (fopen_retry_cnt > 0)) { fopen_retry_cnt--; goto retry_fopen; } return (0); } /* * Read ctids from file into 2 lists: * - temporary list to be traversed (uup) * - cached list that can be modified if adoption of * contract fails (inst->start_ctids). * Always treat ctids as long longs. */ uup = create_rep_val_list(); /* fscanf may not set errno, so clear it first */ errno = 0; while ((fscanf_ret = fscanf(fp, "%llu", &tval)) == 1) { /* If tval isn't a valid ctid, then fail. */ if (tval == 0) { (void) fclose(fp); ret = EIO; goto out; } if ((add_rep_val(uup, tval) == -1) || (add_rep_val(inst->start_ctids, tval) == -1)) { (void) fclose(fp); ret = ENOMEM; goto out; } errno = 0; } /* EOF is not a failure when no errno */ if ((fscanf_ret != EOF) || (errno != 0)) { ret = EIO; goto out; } if (fclose(fp) != 0) { ret = ENXIO; goto out; } for (spval = uu_list_first(uup); spval != NULL; spval = uu_list_next(uup, spval)) { /* Try to adopt the contract */ if (adopt_contract((ctid_t)spval->val, inst->fmri) != 0) { /* * Adoption failed. No reason to think it'll * work later, so remove the id from our list * in the instance. */ remove_rep_val(inst->start_ctids, spval->val); } } out: if (uup) { empty_rep_val_list(uup); destroy_rep_val_list(uup); } if (ret != 0) empty_rep_val_list(inst->start_ctids); return (ret); } #!/bin/sh # # 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. # echo_file usr/src/cmd/Makefile.ctf /* * 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * Contains routines that deal with TLI/XTI endpoints and rpc services. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" uu_list_pool_t *conn_ind_pool = NULL; /* * RPC functions. */ /* * Returns B_TRUE if the non-address components of the 2 rpc_info_t structures * are equivalent, else B_FALSE. */ boolean_t rpc_info_equal(const rpc_info_t *ri, const rpc_info_t *ri2) { return ((ri->prognum == ri2->prognum) && (ri->lowver == ri2->lowver) && (ri->highver == ri2->highver) && (strcmp(ri->netid, ri2->netid) == 0)); } /* * Determine if we have a configured interface for the specified address * family. This code is a mirror of libnsl's __can_use_af(). We mirror * it because we need an exact duplicate of its behavior, yet the * function isn't exported by libnsl, and this fix is considered short- * term, so it's not worth exporting it. * * We need to duplicate __can_use_af() so we can accurately determine * when getnetconfigent() returns failure for a v6 netid due to no IPv6 * interfaces being configured: getnetconfigent() returns failure * if a netid is either 'tcp6' or 'udp6' and __can_use_af() returns 0, * but it doesn't return a return code to uniquely determine this * failure. If we don't accurately determine these failures, we could * output error messages in a case when they weren't justified. */ static int can_use_af(sa_family_t af) { struct lifnum lifn; int fd; if ((fd = open("/dev/udp", O_RDONLY)) < 0) { return (0); } lifn.lifn_family = af; /* LINTED ECONST_EXPR */ lifn.lifn_flags = IFF_UP & !(IFF_NOXMIT | IFF_DEPRECATED); if (ioctl(fd, SIOCGLIFNUM, &lifn, sizeof (lifn)) < 0) { lifn.lifn_count = 0; } (void) close(fd); return (lifn.lifn_count); } static boolean_t is_v6_netid(const char *netid) { return ((strcmp(netid, SOCKET_PROTO_TCP6) == 0) || (strcmp(netid, SOCKET_PROTO_UDP6) == 0)); } /* * Registers with rpcbind the program number with all versions, from low to * high, with the netid, all specified in 'rpc'. If registration fails, * returns -1, else 0. */ int register_rpc_service(const char *fmri, const rpc_info_t *rpc) { struct netconfig *nconf; int ver; if ((nconf = getnetconfigent(rpc->netid)) == NULL) { /* * Check whether getnetconfigent() failed as a result of * having no IPv6 interfaces configured for a v6 netid, or * as a result of a 'real' error, and output an appropriate * message with an appropriate severity. */ if (is_v6_netid(rpc->netid) && !can_use_af(AF_INET6)) { warn_msg(gettext( "Couldn't register netid %s for RPC instance %s " "because no IPv6 interfaces are plumbed"), rpc->netid, fmri); } else { error_msg(gettext( "Failed to lookup netid '%s' for instance %s: %s"), rpc->netid, fmri, nc_sperror()); } return (-1); } for (ver = rpc->lowver; ver <= rpc->highver; ver++) { if (!rpcb_set(rpc->prognum, ver, nconf, &(rpc->netbuf))) { error_msg(gettext("Failed to register version %d " "of RPC service instance %s, netid %s"), ver, fmri, rpc->netid); for (ver--; ver >= rpc->lowver; ver--) (void) rpcb_unset(rpc->prognum, ver, nconf); freenetconfigent(nconf); return (-1); } } freenetconfigent(nconf); return (0); } /* Unregister all the registrations done by register_rpc_service */ void unregister_rpc_service(const char *fmri, const rpc_info_t *rpc) { int ver; struct netconfig *nconf; if ((nconf = getnetconfigent(rpc->netid)) == NULL) { /* * Don't output an error message if getnetconfigent() fails for * a v6 netid when an IPv6 interface isn't configured. */ if (!(is_v6_netid(rpc->netid) && !can_use_af(AF_INET6))) { error_msg(gettext( "Failed to lookup netid '%s' for instance %s: %s"), rpc->netid, fmri, nc_sperror()); } return; } for (ver = rpc->lowver; ver <= rpc->highver; ver++) (void) rpcb_unset(rpc->prognum, ver, nconf); freenetconfigent(nconf); } /* * TLI/XTI functions. */ int tlx_init(void) { if ((conn_ind_pool = uu_list_pool_create("conn_ind_pool", sizeof (tlx_conn_ind_t), offsetof(tlx_conn_ind_t, link), NULL, UU_LIST_POOL_DEBUG)) == NULL) { error_msg("%s: %s", gettext("Failed to create uu pool"), uu_strerror(uu_error())); return (-1); } return (0); } void tlx_fini(void) { if (conn_ind_pool != NULL) { uu_list_pool_destroy(conn_ind_pool); conn_ind_pool = NULL; } } /* * Checks if the contents of the 2 tlx_info_t structures are equivalent. * If 'isrpc' is false, the address components of the two structures are * compared for equality as part of this. If the two structures are * equivalent B_TRUE is returned, else B_FALSE. */ boolean_t tlx_info_equal(const tlx_info_t *ti, const tlx_info_t *ti2, boolean_t isrpc) { return ((isrpc || (memcmp(ti->local_addr.buf, ti2->local_addr.buf, sizeof (struct sockaddr_storage)) == 0)) && (strcmp(ti->dev_name, ti2->dev_name) == 0)); } /* * Attempts to bind an address to the network fd 'fd'. If 'reqaddr' is non-NULL, * it attempts to bind to that requested address, else it binds to a kernel * selected address. In the former case, the function returning success * doesn't guarantee that the requested address was bound (the caller needs to * check). If 'retaddr' is non-NULL, the bound address is returned in it. The * 'qlen' parameter is used to set the connection backlog. If the bind * succeeds 0 is returned, else -1. */ static int tlx_bind(int fd, const struct netbuf *reqaddr, struct netbuf *retaddr, int qlen) { struct t_bind breq; struct t_bind bret; if (retaddr != NULL) { /* caller requests bound address be returned */ bret.addr.buf = retaddr->buf; bret.addr.maxlen = retaddr->maxlen; } if (reqaddr != NULL) { /* caller requests specific address */ breq.addr.buf = reqaddr->buf; breq.addr.len = reqaddr->len; } else { breq.addr.len = 0; } breq.qlen = qlen; if (t_bind(fd, &breq, retaddr != NULL ? &bret : NULL) < 0) return (-1); if (retaddr != NULL) retaddr->len = bret.addr.len; return (0); } static int tlx_setsockopt(int fd, int level, int optname, const void *optval, socklen_t optlen) { struct t_optmgmt request, reply; struct { struct opthdr sockopt; char data[256]; } optbuf; if (optlen > sizeof (optbuf.data)) { error_msg(gettext("t_optmgmt request too long")); return (-1); } optbuf.sockopt.level = level; optbuf.sockopt.name = optname; optbuf.sockopt.len = optlen; (void) memcpy(optbuf.data, optval, optlen); request.opt.len = sizeof (struct opthdr) + optlen; request.opt.buf = (char *)&optbuf; request.flags = T_NEGOTIATE; reply.opt.maxlen = sizeof (struct opthdr) + optlen; reply.opt.buf = (char *)&optbuf; reply.flags = 0; if ((t_optmgmt(fd, &request, &reply) == -1) || (reply.flags != T_SUCCESS)) { error_msg("t_optmgmt: %s", t_strerror(t_errno)); return (-1); } return (0); } /* * Compare contents of netbuf for equality. Return B_TRUE on a match and * B_FALSE for mismatch. */ static boolean_t netbufs_equal(struct netbuf *n1, struct netbuf *n2) { return ((n1->len == n2->len) && (memcmp(n1->buf, n2->buf, (size_t)n1->len) == 0)); } /* * Create a tli/xti endpoint, either bound to the address specified in * 'instance' for non-RPC services, else a kernel chosen address. * Returns -1 on failure, else 0. */ int create_bound_endpoint(const instance_t *inst, tlx_info_t *tlx_info) { int fd; int qlen; const char *fmri = inst->fmri; struct netbuf *reqaddr; struct netbuf *retaddr; struct netbuf netbuf; struct sockaddr_storage ss; rpc_info_t *rpc = tlx_info->pr_info.ri; if ((fd = t_open(tlx_info->dev_name, O_RDWR, NULL)) == -1) { error_msg(gettext("Failed to open transport %s for " "instance %s, proto %s: %s"), tlx_info->dev_name, fmri, tlx_info->pr_info.proto, t_strerror(t_errno)); return (-1); } if (tlx_info->pr_info.v6only) { int on = 1; /* restrict to IPv6 communications only */ if (tlx_setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof (on)) == -1) { (void) t_close(fd); return (-1); } } /* * Negotiate for the returning of the remote uid for loopback * transports for RPC services. This needs to be done before the * endpoint is bound using t_bind(), so that any requests to it * contain the uid. */ if ((rpc != NULL) && (rpc->is_loopback)) svc_fd_negotiate_ucred(fd); /* * Bind the service's address to the endpoint and setup connection * backlog. In the case of RPC services, we specify a NULL requested * address and accept what we're given, storing the returned address * for later RPC binding. In the case of non-RPC services we specify * the service's associated address. */ if (rpc != NULL) { reqaddr = NULL; retaddr = &(rpc->netbuf); } else { reqaddr = &(tlx_info->local_addr); netbuf.buf = (char *)&ss; netbuf.maxlen = sizeof (ss); retaddr = &netbuf; } /* ignored for conn/less services */ qlen = inst->config->basic->conn_backlog; if ((tlx_bind(fd, reqaddr, retaddr, qlen) == -1) || ((reqaddr != NULL) && !netbufs_equal(reqaddr, retaddr))) { error_msg(gettext("Failed to bind to the requested address " "for instance %s, proto %s"), fmri, tlx_info->pr_info.proto); (void) t_close(fd); return (-1); } return (fd); } /* * Takes a connection request off 'fd' in the form of a t_call structure * and returns a pointer to it. * Returns NULL on failure, else pointer to t_call structure on success. */ static struct t_call * get_new_conind(int fd) { struct t_call *call; /* LINTED E_BAD_PTR_CAST_ALIGN */ if ((call = (struct t_call *)t_alloc(fd, T_CALL, T_ALL)) == NULL) { error_msg("t_alloc: %s", t_strerror(t_errno)); return (NULL); } if (t_listen(fd, call) < 0) { error_msg("t_listen: %s", t_strerror(t_errno)); (void) t_free((char *)call, T_CALL); return (NULL); } return (call); } /* Add 'call' to the connection indication queue 'queue'. */ int queue_conind(uu_list_t *queue, struct t_call *call) { tlx_conn_ind_t *ci; if ((ci = malloc(sizeof (tlx_conn_ind_t))) == NULL) { error_msg(strerror(errno)); return (-1); } ci->call = call; uu_list_node_init(ci, &ci->link, conn_ind_pool); (void) uu_list_insert_after(queue, NULL, ci); return (0); } /* * Remove and return a pointer to the first call on queue 'queue'. However, * if the queue is empty returns NULL. */ struct t_call * dequeue_conind(uu_list_t *queue) { struct t_call *ret; tlx_conn_ind_t *ci = uu_list_first(queue); if (ci == NULL) return (NULL); ret = ci->call; uu_list_remove(queue, ci); free(ci); return (ret); } /* * Handle a TLOOK notification received during a t_accept() call. * Returns -1 on failure, else 0. */ static int process_tlook(const char *fmri, tlx_info_t *tlx_info) { int event; int fd = tlx_info->pr_info.listen_fd; switch (event = t_look(fd)) { case T_LISTEN: { struct t_call *call; debug_msg("process_tlook: T_LISTEN event"); if ((call = get_new_conind(fd)) == NULL) return (-1); if (queue_conind(tlx_info->conn_ind_queue, call) == -1) { error_msg(gettext("Failed to queue connection " "indication for instance %s"), fmri); (void) t_free((char *)call, T_CALL); return (-1); } break; } case T_DISCONNECT: { /* * Note: In Solaris 2.X (SunOS 5.X) bundled * connection-oriented transport drivers * [ e.g /dev/tcp and /dev/ticots and * /dev/ticotsord (tl)] we do not send disconnect * indications to listening endpoints. * So this will not be seen with endpoints on Solaris * bundled transport devices. However, Streams TPI * allows for this (broken?) behavior and so we account * for it here because of the possibility of unbundled * transport drivers causing this. */ tlx_conn_ind_t *cip; struct t_discon *discon; debug_msg("process_tlook: T_DISCONNECT event"); /* LINTED */ if ((discon = (struct t_discon *) t_alloc(fd, T_DIS, T_ALL)) == NULL) { error_msg("t_alloc: %s", t_strerror(t_errno)); return (-1); } if (t_rcvdis(fd, discon) < 0) { error_msg("t_rcvdis: %s", t_strerror(t_errno)); (void) t_free((char *)discon, T_DIS); return (-1); } /* * Find any queued connection pending that matches this * disconnect notice and remove from the pending queue. */ cip = uu_list_first(tlx_info->conn_ind_queue); while ((cip != NULL) && (cip->call->sequence != discon->sequence)) { cip = uu_list_next(tlx_info->conn_ind_queue, cip); } if (cip != NULL) { /* match found */ uu_list_remove(tlx_info->conn_ind_queue, cip); (void) t_free((char *)cip->call, T_CALL); free(cip); } (void) t_free((char *)discon, T_DIS); break; } case -1: error_msg("t_look: %s", t_strerror(t_errno)); return (-1); default: error_msg(gettext("do_tlook: unexpected t_look event: %d"), event); return (-1); } return (0); } /* * This call attempts to t_accept() an incoming/pending TLI connection. * If it is thwarted by a TLOOK, it is deferred and whatever is on the * file descriptor, removed after a t_look. (Incoming connect indications * get queued for later processing and disconnect indications remove a * a queued connection request if a match found). * Returns -1 on failure, else 0. */ int tlx_accept(const char *fmri, tlx_info_t *tlx_info, struct sockaddr_storage *remote_addr) { tlx_conn_ind_t *conind; struct t_call *call; int fd; int listen_fd = tlx_info->pr_info.listen_fd; if ((fd = t_open(tlx_info->dev_name, O_RDWR, NULL)) == -1) { error_msg("t_open: %s", t_strerror(t_errno)); return (-1); } if (tlx_info->pr_info.v6only) { int on = 1; /* restrict to IPv6 communications only */ if (tlx_setsockopt(fd, IPPROTO_IPV6, IPV6_V6ONLY, &on, sizeof (on)) == -1) { (void) t_close(fd); return (-1); } } if (t_bind(fd, NULL, NULL) == -1) { error_msg("t_bind: %s", t_strerror(t_errno)); (void) t_close(fd); return (-1); } /* * Get the next connection indication - first try the pending * queue, then, if none there, get a new one from the file descriptor. */ if ((conind = uu_list_first(tlx_info->conn_ind_queue)) != NULL) { debug_msg("taking con off queue"); call = conind->call; } else if ((call = get_new_conind(listen_fd)) == NULL) { (void) t_close(fd); return (-1); } /* * Accept the connection indication on the newly created endpoint. * If we fail, and it's the result of a tlook, queue the indication * if it isn't already, and go and process the t_look. */ if (t_accept(listen_fd, fd, call) == -1) { if (t_errno == TLOOK) { if (uu_list_first(tlx_info->conn_ind_queue) == NULL) { /* * We are first one to have to defer accepting * and start the pending connections list. */ if (queue_conind(tlx_info->conn_ind_queue, call) == -1) { error_msg(gettext( "Failed to queue connection " "indication for instance %s"), fmri); (void) t_free((char *)call, T_CALL); return (-1); } } (void) process_tlook(fmri, tlx_info); } else { /* non-TLOOK accept failure */ error_msg("%s: %s", "t_accept failed", t_strerror(t_errno)); /* * If we were accepting a queued connection, dequeue * it. */ if (uu_list_first(tlx_info->conn_ind_queue) != NULL) (void) dequeue_conind(tlx_info->conn_ind_queue); (void) t_free((char *)call, T_CALL); } (void) t_close(fd); return (-1); } /* Copy remote address into address parameter */ (void) memcpy(remote_addr, call->addr.buf, MIN(call->addr.len, sizeof (*remote_addr))); /* If we were accepting a queued connection, dequeue it. */ if (uu_list_first(tlx_info->conn_ind_queue) != NULL) (void) dequeue_conind(tlx_info->conn_ind_queue); (void) t_free((char *)call, T_CALL); return (fd); } /* protocol independent network fd close routine */ void close_net_fd(instance_t *inst, int fd) { if (inst->config->basic->istlx) { (void) t_close(fd); } else { (void) close(fd); } } /* * Consume some data from the given endpoint of the given wait-based instance. */ void consume_wait_data(instance_t *inst, int fd) { int flag; char buf[50]; /* same arbitrary size as old inetd */ if (inst->config->basic->istlx) { (void) t_rcv(fd, buf, sizeof (buf), &flag); } else { (void) recv(fd, buf, sizeof (buf), 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 2008 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * General utility routines. */ #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* size of buffer used in msg() to expand printf() like messages into */ #define MSG_BUF_SIZE 1024 /* number of pollfd we grow the pollfd array by at a time in set_pollfd() */ #define POLLFDS_GROWTH_SIZE 16 /* enumeration of message types supported by msg() */ typedef enum { MT_ERROR, MT_DEBUG, MT_WARN } si_msg_type_t; /* * Collection of information for each method type. * NOTE: This table is indexed into using the instance_method_t * enumeration, so the ordering needs to be kept in synch. */ method_type_info_t methods[] = { {IM_START, START_METHOD_NAME, IIS_NONE}, {IM_ONLINE, ONLINE_METHOD_NAME, IIS_ONLINE}, {IM_OFFLINE, OFFLINE_METHOD_NAME, IIS_OFFLINE}, {IM_DISABLE, DISABLE_METHOD_NAME, IIS_DISABLED}, {IM_REFRESH, REFRESH_METHOD_NAME, IIS_ONLINE}, {IM_NONE, "none", IIS_NONE} }; struct pollfd *poll_fds = NULL; nfds_t num_pollfds; boolean_t syslog_open = B_FALSE; boolean_t debug_enabled = B_FALSE; void msg_init(void) { openlog(SYSLOG_IDENT, LOG_PID|LOG_CONS, LOG_DAEMON); syslog_open = B_TRUE; } void msg_fini(void) { syslog_open = B_FALSE; closelog(); } /* * Outputs a msg. If 'type' is set tp MT_ERROR or MT_WARN the message goes * to syslog with severitys LOG_ERROR and LOG_WARN respectively. For all * values of 'type' the message is written to the debug log file, if it * was openable when inetd started. */ static void msg(si_msg_type_t type, const char *format, va_list ap) { /* * Use a stack buffer so we stand more chance of reporting a * memory shortage failure. */ char buf[MSG_BUF_SIZE]; if (!syslog_open) return; (void) vsnprintf(buf, sizeof (buf), format, ap); /* * Log error and warning messages to syslog with appropriate severity. */ if (type == MT_ERROR) { syslog(LOG_ERR, "%s", buf); } else if (type == MT_WARN) { syslog(LOG_WARNING, "%s", buf); } else if (debug_enabled && type == MT_DEBUG) { syslog(LOG_DEBUG, "%s", buf); } } /* * Output a warning message. Unlike error_msg(), syslog doesn't get told * to log to the console if syslogd isn't around. */ void warn_msg(const char *format, ...) { va_list ap; closelog(); openlog(SYSLOG_IDENT, LOG_PID, LOG_DAEMON); va_start(ap, format); msg(MT_WARN, format, ap); va_end(ap); closelog(); openlog(SYSLOG_IDENT, LOG_PID|LOG_CONS, LOG_DAEMON); } void debug_msg(const char *format, ...) { va_list ap; va_start(ap, format); msg(MT_DEBUG, format, ap); va_end(ap); } void error_msg(const char *format, ...) { va_list ap; va_start(ap, format); msg(MT_ERROR, format, ap); va_end(ap); } void poll_fini(void) { if (poll_fds != NULL) { free(poll_fds); poll_fds = NULL; } } struct pollfd * find_pollfd(int fd) { nfds_t n; for (n = 0; n < num_pollfds; n++) { if (poll_fds[n].fd == fd) return (&(poll_fds[n])); } return (NULL); } int set_pollfd(int fd, uint16_t events) { struct pollfd *p; int i; p = find_pollfd(fd); if ((p == NULL) && ((p = find_pollfd(-1)) == NULL)) { if ((p = realloc(poll_fds, ((num_pollfds + POLLFDS_GROWTH_SIZE) * sizeof (struct pollfd)))) == NULL) { return (-1); } poll_fds = p; for (i = 1; i < POLLFDS_GROWTH_SIZE; i++) poll_fds[num_pollfds + i].fd = -1; p = &poll_fds[num_pollfds]; num_pollfds += POLLFDS_GROWTH_SIZE; } p->fd = fd; p->events = events; p->revents = 0; return (0); } void clear_pollfd(int fd) { struct pollfd *p; if ((p = find_pollfd(fd)) != NULL) { p->fd = -1; p->events = 0; p->revents = 0; } } boolean_t isset_pollfd(int fd) { struct pollfd *p = find_pollfd(fd); return ((p != NULL) && (p->revents & POLLIN)); } /* * An extension of read() that keeps retrying until either the full request has * completed, the other end of the connection/pipe is closed, no data is * readable for a non-blocking socket/pipe, or an unexpected error occurs. * Returns 0 if the data is successfully read, 1 if the other end of the pipe/ * socket is closed or there's nothing to read from a non-blocking socket/pipe, * else -1 if an unexpected error occurs. */ int safe_read(int fd, void *buf, size_t sz) { int ret; size_t cnt = 0; char *cp = (char *)buf; if (sz == 0) return (0); do { switch (ret = read(fd, cp + cnt, sz - cnt)) { case 0: /* other end of pipe/socket closed */ return (1); case -1: if (errno == EAGAIN) { /* nothing to read */ return (1); } else if (errno != EINTR) { error_msg(gettext("Unexpected read error: %s"), strerror(errno)); return (-1); } break; default: cnt += ret; } } while (cnt != sz); return (0); } /* * Return B_TRUE if instance 'inst' has exceeded its configured maximum * concurrent copies limit, else B_FALSE. */ boolean_t copies_limit_exceeded(instance_t *inst) { /* any value <=0 means that copies limits are disabled */ return ((inst->config->basic->max_copies > 0) && (inst->copies >= inst->config->basic->max_copies)); } /* * Cancel the method/con-rate offline timer associated with the instance. */ void cancel_inst_timer(instance_t *inst) { (void) iu_cancel_timer(timer_queue, inst->timer_id, NULL); inst->timer_id = -1; } /* * Cancel the bind retry timer associated with the instance. */ void cancel_bind_timer(instance_t *inst) { (void) iu_cancel_timer(timer_queue, inst->bind_timer_id, NULL); inst->bind_timer_id = -1; } void enable_blocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); (void) fcntl(fd, F_SETFL, (flags & ~O_NONBLOCK)); } void disable_blocking(int fd) { int flags = fcntl(fd, F_GETFL, 0); (void) fcntl(fd, F_SETFL, (flags | O_NONBLOCK)); } /* * 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. */ /* * This file contains a set of routines used to perform wait based method * reaping. */ #include #include #include #include #include #include #include #include #include #include #include "inetd_impl.h" /* inetd's open file limit, set in method_init() */ #define INETD_NOFILE_LIMIT RLIM_INFINITY /* structure used to represent an active method process */ typedef struct { int fd; /* fd of process's /proc psinfo file */ /* associated contract id if known, else -1 */ ctid_t cid; pid_t pid; instance_t *inst; /* pointer to associated instance */ instance_method_t method; /* the method type running */ /* associated endpoint protocol name if known, else NULL */ char *proto_name; uu_list_node_t link; } method_el_t; static void unregister_method(method_el_t *); /* list of currently executing method processes */ static uu_list_pool_t *method_pool = NULL; static uu_list_t *method_list = NULL; /* * File limit saved during initialization before modification, so that it can * be reverted back to for inetd's exec'd methods. */ static struct rlimit saved_file_limit; /* * Setup structures used for method termination monitoring. * Returns -1 if an allocation failure occurred, else 0. */ int method_init(void) { struct rlimit rl; /* * Save aside the old file limit and impose one large enough to support * all the /proc file handles we could have open. */ (void) getrlimit(RLIMIT_NOFILE, &saved_file_limit); rl.rlim_cur = rl.rlim_max = INETD_NOFILE_LIMIT; if (setrlimit(RLIMIT_NOFILE, &rl) == -1) { error_msg("Failed to set file limit: %s", strerror(errno)); return (-1); } if ((method_pool = uu_list_pool_create("method_pool", sizeof (method_el_t), offsetof(method_el_t, link), NULL, UU_LIST_POOL_DEBUG)) == NULL) { error_msg("%s: %s", gettext("Failed to create method pool"), uu_strerror(uu_error())); return (-1); } if ((method_list = uu_list_create(method_pool, NULL, 0)) == NULL) { error_msg("%s: %s", gettext("Failed to create method list"), uu_strerror(uu_error())); /* let method_fini() clean-up */ return (-1); } return (0); } /* * Tear-down structures created in method_init(). */ void method_fini(void) { if (method_list != NULL) { method_el_t *me; while ((me = uu_list_first(method_list)) != NULL) unregister_method(me); (void) uu_list_destroy(method_list); method_list = NULL; } if (method_pool != NULL) { (void) uu_list_pool_destroy(method_pool); method_pool = NULL; } /* revert file limit */ method_preexec(); } /* * Revert file limit back to pre-initialization one. This shouldn't fail as * long as its called *after* descriptor cleanup. */ void method_preexec(void) { (void) setrlimit(RLIMIT_NOFILE, &saved_file_limit); } /* * Callback function that handles the timeout of an instance's method. * 'arg' points at the method_el_t representing the method. */ /* ARGSUSED0 */ static void method_timeout(iu_tq_t *tq, void *arg) { method_el_t *mp = arg; error_msg(gettext("The %s method of instance %s timed-out"), methods[mp->method].name, mp->inst->fmri); mp->inst->timer_id = -1; if (mp->method == IM_START) { process_start_term(mp->inst, mp->proto_name); } else { process_non_start_term(mp->inst, IMRET_FAILURE); } unregister_method(mp); } /* * Registers the attributes of a running method passed as arguments so that * the method's termination is noticed and any further processing of the * associated instance is carried out. The function also sets up any * necessary timers so we can detect hung methods. * Returns -1 if either it failed to open the /proc psinfo file which is used * to monitor the method process, it failed to setup a required timer or * memory allocation failed; else 0. */ int register_method(instance_t *ins, pid_t pid, ctid_t cid, instance_method_t mthd, char *proto_name) { char path[MAXPATHLEN]; int fd; method_el_t *me; /* open /proc psinfo file of process to listen for POLLHUP events on */ (void) snprintf(path, sizeof (path), "/proc/%u/psinfo", pid); for (;;) { if ((fd = open(path, O_RDONLY)) >= 0) { break; } else if (errno != EINTR) { /* * Don't output an error for ENOENT; we get this * if a method has gone away whilst we were stopped, * and we're now trying to re-listen for it. */ if (errno != ENOENT) { error_msg(gettext("Failed to open %s: %s"), path, strerror(errno)); } return (-1); } } /* add method record to in-memory list */ if ((me = calloc(1, sizeof (method_el_t))) == NULL) { error_msg(strerror(errno)); (void) close(fd); return (-1); } me->fd = fd; me->inst = (instance_t *)ins; me->method = mthd; me->pid = pid; me->cid = cid; if (proto_name != NULL) { if ((me->proto_name = strdup(proto_name)) == NULL) { error_msg(strerror(errno)); free(me); (void) close(fd); return (-1); } } else me->proto_name = NULL; /* register a timeout for the method, if required */ if (mthd != IM_START) { method_info_t *mi = ins->config->methods[mthd]; if (mi->timeout > 0) { assert(ins->timer_id == -1); ins->timer_id = iu_schedule_timer(timer_queue, mi->timeout, method_timeout, me); if (ins->timer_id == -1) { error_msg(gettext( "Failed to schedule method timeout")); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } } } /* * Add fd of psinfo file to poll set, but pass 0 for events to * poll for, so we should only get a POLLHUP event on the fd. */ if (set_pollfd(fd, 0) == -1) { cancel_inst_timer(ins); if (me->proto_name != NULL) free(me->proto_name); free(me); (void) close(fd); return (-1); } uu_list_node_init(me, &me->link, method_pool); (void) uu_list_insert_after(method_list, NULL, me); return (0); } /* * A counterpart to register_method(), this function stops the monitoring of a * method process for its termination. */ static void unregister_method(method_el_t *me) { /* cancel any timer associated with the method */ if (me->inst->timer_id != -1) cancel_inst_timer(me->inst); /* stop polling on the psinfo file fd */ clear_pollfd(me->fd); (void) close(me->fd); /* remove method record from list */ uu_list_remove(method_list, me); if (me->proto_name != NULL) free(me->proto_name); free(me); } /* * Unregister all methods associated with instance 'inst'. */ void unregister_instance_methods(const instance_t *inst) { method_el_t *me = uu_list_first(method_list); while (me != NULL) { if (me->inst == inst) { method_el_t *tmp = me; me = uu_list_next(method_list, me); unregister_method(tmp); } else { me = uu_list_next(method_list, me); } } } /* * Process any terminated methods. For each method determined to have * terminated, the function determines its return value and calls the * appropriate handling function, depending on the type of the method. */ void process_terminated_methods(void) { method_el_t *me = uu_list_first(method_list); while (me != NULL) { struct pollfd *pfd; pid_t pid; int status; int ret; method_el_t *tmp; pfd = find_pollfd(me->fd); /* * We expect to get a POLLHUP back on the fd of the process's * open psinfo file from /proc when the method terminates. * A POLLERR could(?) mask a POLLHUP, so handle this * also. */ if ((pfd->revents & (POLLHUP|POLLERR)) == 0) { me = uu_list_next(method_list, me); continue; } /* get the method's exit code (no need to loop for EINTR) */ pid = waitpid(me->pid, &status, WNOHANG); switch (pid) { case 0: /* child still around */ /* * Either poll() is sending us invalid POLLHUP events * or is flagging a POLLERR on the fd. Neither should * happen, but in the event they do, ignore this fd * this time around and wait out the termination * of its associated method. This may result in * inetd swiftly looping in event_loop(), but means * we don't miss the termination of a method. */ me = uu_list_next(method_list, me); continue; case -1: /* non-existent child */ assert(errno == ECHILD); /* * the method must not be owned by inetd due to it * persisting over an inetd restart. Let's assume the * best, that it was successful. */ ret = IMRET_SUCCESS; break; default: /* child terminated */ if (WIFEXITED(status)) { ret = WEXITSTATUS(status); debug_msg("process %ld of instance %s returned " "%d", pid, me->inst->fmri, ret); } else if (WIFSIGNALED(status)) { /* * Terminated by signal. This may be due * to a kill that we sent from a disable or * offline event. We flag it as a failure, but * this flagged failure will only be processed * in the case of non-start methods, or when * the instance is still enabled. */ debug_msg("process %ld of instance %s exited " "due to signal %d", pid, me->inst->fmri, WTERMSIG(status)); ret = IMRET_FAILURE; } else { /* * Can we actually get here? Don't think so. * Treat it as a failure, anyway. */ debug_msg("waitpid() for %s method of " "instance %s returned %d", methods[me->method].name, me->inst->fmri, status); ret = IMRET_FAILURE; } } remove_method_ids(me->inst, me->pid, me->cid, me->method); /* continue state transition processing of the instance */ if (me->method != IM_START) { process_non_start_term(me->inst, ret); } else { process_start_term(me->inst, me->proto_name); } if (me->cid != -1) (void) abandon_contract(me->cid); tmp = me; me = uu_list_next(method_list, me); unregister_method(tmp); } } # # 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 2007 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # Copyright 2016 Toomas Soome # # Copyright (c) 2018, Joyent, Inc. PROG= mdnsd MANIFEST= multicast.xml SRCDIR= $(SRC)/contrib/mDNSResponder OBJS= DNSCommon.o DNSDigest.o GenLinkedList.o \ PlatformCommon.o PosixDaemon.o \ mDNS.o mDNSDebug.o mDNSPosix.o mDNSUNP.o \ uDNS.o uds_daemon.o dnssd_ipc.o posix_utilities.o \ ClientRequests.o SRCS= $(OBJS:%.o=%.c) MDNSFLAGS= -DNOT_HAVE_SA_LEN \ -DLOG_PERROR=0 -DHAVE_SOLARIS -DTARGET_OS_SOLARIS \ -D_XPG4_2 -D__EXTENSIONS__ -DHAVE_BROKEN_RECVIF_NAME \ -DHAVE_IPV6=1 -Dasm=__asm -DMDNSD_NOROOT \ -DPID_FILE=\"\" -DMDNSD_USER=\"noaccess\" \ -DmDNSResponderVersion=1310.80.1 include ../../../Makefile.cmd CERRWARN += -Wno-unused-variable CERRWARN += -Wno-implicit-function-declaration CERRWARN += -Wno-expansion-to-defined CERRWARN += $(CNOWARN_UNINIT) # not linted SMATCH=off ROOTMANIFESTDIR= $(ROOTSVCNETWORKDNS) $(ROOTMANIFEST) : FILEMODE= 444 .PARALLEL: $(OBJS) .WAIT: $(PROG) .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib CSTD= $(CSTD_GNU99) CPPFLAGS += -D_REENTRANT $(MDNSFLAGS) CPPFLAGS += -I$(SRCDIR)/mDNSShared -I$(SRCDIR)/mDNSPosix CPPFLAGS += -I$(SRCDIR)/mDNSCore LDLIBS += -lsocket -lnsl install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST) %.o: $(SRCDIR)/mDNSCore/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) %.o: $(SRCDIR)/mDNSShared/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) %.o: $(SRCDIR)/mDNSPosix/%.c $(COMPILE.c) $(OUTPUT_OPTION) $< $(POST_PROCESS_O) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) include ../../../Makefile.targ # # 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-2002 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. include ../../../Makefile.cmd PROG= pppoec pppoed CLIENT_OBJS= pppoec.o common.o logging.o DAEMON_OBJS= pppoed.o options.o logging.o common.o CPPFLAGS += -I$(SRC)/uts/common # not linted SMATCH=off .KEEP_STATE: all: $(PROG) OBJS= $(CLIENT_OBJS) $(DAEMON_OBJS) LDLIBS += -lsocket -lnsl .PARALLEL: $(OBJS) pppoec: $(CLIENT_OBJS) $(LINK.c) $(CLIENT_OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) pppoed: $(DAEMON_OBJS) $(LINK.c) $(DAEMON_OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib install: all .WAIT $(PROG) $(ROOTLIBINETPROG) clean: $(RM) $(OBJS) lint: $(LINT.c) $(CLIENT_OBJS:%.o=%.c) $(LDLIBS) $(LINT.c) $(DAEMON_OBJS:%.o=%.c) $(LDLIBS) include ../../../Makefile.targ /* * 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 */ /* * PPPoE common utilities and data. * * Copyright 2005 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 "common.h" /* Not all functions are used by all applications. Let lint know this. */ /*LINTLIBRARY*/ /* Common I/O buffers */ uint32_t pkt_input[PKT_INPUT_LEN / sizeof (uint32_t)]; uint32_t pkt_octl[PKT_OCTL_LEN / sizeof (uint32_t)]; uint32_t pkt_output[PKT_OUTPUT_LEN / sizeof (uint32_t)]; const char tunnam[] = "/dev/" PPP_TUN_NAME; const ether_addr_t ether_bcast = { 0xFF, 0xFF, 0xFF, 0xFF, 0xFF, 0xFF }; /* * Wrapper for standard strerror() function -- the standard allows * that routine to return NULL, and that's inconvenient to handle. * This function never returns NULL. */ const char * mystrerror(int err) { const char *estr; static char ebuf[64]; if ((estr = strerror(err)) != NULL) return (estr); (void) snprintf(ebuf, sizeof (ebuf), "Error:%d", err); return (ebuf); } /* * Wrapper for standard perror() function -- the standard definition * of perror doesn't include the program name in the output and is * thus inconvenient to use. */ void myperror(const char *emsg) { (void) fprintf(stderr, "%s: %s: %s\n", myname, emsg, mystrerror(errno)); } /* * Wrapper for standard getmsg() function. Completely discards any * fragmented messages because we don't expect ever to see these from * a properly functioning tunnel driver. Returns flags * (MORECTL|MOREDATA) as seen by interface. */ int mygetmsg(int fd, struct strbuf *ctrl, struct strbuf *data, int *flags) { int retv; int hadflags; hadflags = getmsg(fd, ctrl, data, flags); if (hadflags <= 0 || !(hadflags & (MORECTL | MOREDATA))) return (hadflags); do { if (flags != NULL) *flags = 0; retv = getmsg(fd, ctrl, data, flags); } while (retv > 0 || (retv < 0 && errno == EINTR)); /* * What remains at this point is the tail end of the * truncated message. Toss it. */ return (retv < 0 ? retv : hadflags); } /* * Common wrapper function for STREAMS I_STR ioctl. Returns -1 on * failure, 0 for success. */ int strioctl(int fd, int cmd, void *ptr, int ilen, int olen) { struct strioctl str; str.ic_cmd = cmd; str.ic_timout = 0; /* Default timeout; 15 seconds */ str.ic_len = ilen; str.ic_dp = ptr; if (ioctl(fd, I_STR, &str) == -1) { return (-1); } if (str.ic_len != olen) { errno = EINVAL; return (-1); } return (0); } /* * Format a PPPoE header in the user's buffer. The returned pointer * is either identical to the first argument, or is NULL if it's not * usable. On entry, dptr should point to the first byte after the * Ethertype field, codeval should be one of the POECODE_* values, and * sessionid should be the assigned session ID number or one of the * special POESESS_* values. */ poep_t * poe_mkheader(void *dptr, uint8_t codeval, int sessionid) { poep_t *poep; /* Discard obvious junk. */ assert(dptr != NULL && IS_P2ALIGNED(dptr, sizeof (poep_t *))); /* Initialize the header */ poep = (poep_t *)dptr; poep->poep_version_type = POE_VERSION; poep->poep_code = codeval; poep->poep_session_id = htons(sessionid); poep->poep_length = htons(0); return (poep); } /* * Validate that a given tag is intact. This is intended to be used * in tag-parsing loops before attempting to access the tag data. */ boolean_t poe_tagcheck(const poep_t *poep, int length, const uint8_t *tptr) { int plen; const uint8_t *tstart, *tend; if (poep == NULL || !IS_P2ALIGNED(poep, sizeof (uint16_t)) || tptr == NULL || length < sizeof (*poep)) return (B_FALSE); plen = poe_length(poep); if (plen + sizeof (*poep) > length) return (B_FALSE); tstart = (const uint8_t *)(poep+1); tend = tstart + plen; /* * Note careful dereference of tptr; it might be near the end * already, so we have to range check it before dereferencing * to get the actual tag length. Yes, it looks like we have * duplicate array end checks. No, they're not duplicates. */ if (tptr < tstart || tptr+POET_HDRLEN > tend || tptr+POET_HDRLEN+POET_GET_LENG(tptr) > tend) return (B_FALSE); return (B_TRUE); } static int poe_tag_insert(poep_t *poep, uint16_t ttype, const void *data, size_t dlen) { int plen; uint8_t *dp; plen = poe_length(poep); if (data == NULL) dlen = 0; if (sizeof (*poep) + plen + POET_HDRLEN + dlen > PPPOE_MSGMAX) return (-1); dp = (uint8_t *)(poep + 1) + plen; POET_SET_TYPE(dp, ttype); POET_SET_LENG(dp, dlen); if (dlen > 0) (void) memcpy(POET_DATA(dp), data, dlen); poep->poep_length = htons(plen + POET_HDRLEN + dlen); return (0); } /* * Add a tag with text string data to a PPPoE packet being * constructed. Returns -1 if it doesn't fit, or 0 for success. */ int poe_add_str(poep_t *poep, uint16_t ttype, const char *str) { return (poe_tag_insert(poep, ttype, str, strlen(str))); } /* * Add a tag with 32-bit integer data to a PPPoE packet being * constructed. Returns -1 if it doesn't fit, or 0 for success. */ int poe_add_long(poep_t *poep, uint16_t ttype, uint32_t val) { val = htonl(val); return (poe_tag_insert(poep, ttype, &val, sizeof (val))); } /* * Add a tag with two 32-bit integers to a PPPoE packet being * constructed. Returns -1 if it doesn't fit, or 0 for success. */ int poe_two_longs(poep_t *poep, uint16_t ttype, uint32_t val1, uint32_t val2) { uint32_t vals[2]; vals[0] = htonl(val1); vals[1] = htonl(val2); return (poe_tag_insert(poep, ttype, vals, sizeof (vals))); } /* * Copy a single tag and its data from one PPPoE packet to a PPPoE * packet being constructed. Returns -1 if it doesn't fit, or 0 for * success. */ int poe_tag_copy(poep_t *poep, const uint8_t *tagp) { int tlen; int plen; tlen = POET_GET_LENG(tagp) + POET_HDRLEN; plen = poe_length(poep); if (sizeof (*poep) + plen + tlen > PPPOE_MSGMAX) return (-1); (void) memcpy((uint8_t *)(poep + 1) + plen, tagp, tlen); poep->poep_length = htons(tlen + plen); return (0); } struct tag_list { int tl_type; const char *tl_name; }; /* List of PPPoE data tag types. */ static const struct tag_list tag_list[] = { { POETT_END, "End-Of-List" }, { POETT_SERVICE, "Service-Name" }, { POETT_ACCESS, "AC-Name" }, { POETT_UNIQ, "Host-Uniq" }, { POETT_COOKIE, "AC-Cookie" }, { POETT_VENDOR, "Vendor-Specific" }, { POETT_RELAY, "Relay-Session-Id" }, { POETT_NAMERR, "Service-Name-Error" }, { POETT_SYSERR, "AC-System-Error" }, { POETT_GENERR, "Generic-Error" }, { POETT_MULTI, "Multicast-Capable" }, { POETT_HURL, "Host-URL" }, { POETT_MOTM, "Message-Of-The-Minute" }, { POETT_RTEADD, "IP-Route-Add" }, { 0, NULL } }; /* List of PPPoE message code numbers. */ static const struct tag_list code_list[] = { { POECODE_DATA, "Data" }, { POECODE_PADO, "Active Discovery Offer" }, { POECODE_PADI, "Active Discovery Initiation" }, { POECODE_PADR, "Active Discovery Request" }, { POECODE_PADS, "Active Discovery Session-confirmation" }, { POECODE_PADT, "Active Discovery Terminate" }, { POECODE_PADM, "Active Discovery Message" }, { POECODE_PADN, "Active Discovery Network" }, { 0, NULL } }; /* * Given a tag type number, return a pointer to a string describing * the tag. */ const char * poe_tagname(uint16_t tagtype) { const struct tag_list *tlp; static char tname[32]; for (tlp = tag_list; tlp->tl_name != NULL; tlp++) if (tagtype == tlp->tl_type) return (tlp->tl_name); (void) sprintf(tname, "Tag%d", tagtype); return (tname); } /* * Given a PPPoE message code number, return a pointer to a string * describing the message. */ const char * poe_codename(uint8_t codetype) { const struct tag_list *tlp; static char tname[32]; for (tlp = code_list; tlp->tl_name != NULL; tlp++) if (codetype == tlp->tl_type) return (tlp->tl_name); (void) sprintf(tname, "Code%d", codetype); return (tname); } /* * Given a tunnel driver address structure, return a pointer to a * string naming that Ethernet host. */ const char * ehost2(const struct ether_addr *ea) { static char hbuf[MAXHOSTNAMELEN+1]; if (ea == NULL) return ("NULL"); if (ether_ntohost(hbuf, ea) == 0) return (hbuf); return (ether_ntoa(ea)); } const char * ehost(const ppptun_atype *pap) { return (ehost2((const struct ether_addr *)pap)); } /* * Given an Internet address (in network byte order), return a pointer * to a string naming the host. */ const char * ihost(uint32_t haddr) { struct hostent *hp; struct sockaddr_in sin; (void) memset(&sin, '\0', sizeof (sin)); sin.sin_addr.s_addr = haddr; hp = gethostbyaddr((const char *)&sin, sizeof (sin), AF_INET); if (hp != NULL) return (hp->h_name); return (inet_ntoa(sin.sin_addr)); } int hexdecode(char chr) { if (chr >= '0' && chr <= '9') return ((int)(chr - '0')); if (chr >= 'a' && chr <= 'f') return ((int)(chr - 'a' + 10)); return ((int)(chr - 'A' + 10)); } /* * 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 */ /* * PPPoE common utilities and data. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef PPPOE_COMMON_H #define PPPOE_COMMON_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #define PKT_INPUT_LEN PPPOE_MSGMAX #define PKT_OCTL_LEN (sizeof (struct ppptun_control) + 1) #define PKT_OUTPUT_LEN PPPOE_MSGMAX /* Common buffers */ extern uint32_t pkt_input[]; extern uint32_t pkt_octl[]; extern uint32_t pkt_output[]; /* Name of PPPoE tunnel driver */ extern const char tunnam[]; /* Name of application (from argv[0]) */ extern char *myname; /* Ethernet broadcast address */ extern const ether_addr_t ether_bcast; /* General purpose utility functions. */ struct strbuf; extern int strioctl(int fd, int cmd, void *ptr, int ilen, int olen); extern const char *ehost(const ppptun_atype *pap); extern const char *ehost2(const struct ether_addr *ea); extern const char *ihost(uint32_t haddr); extern int hexdecode(char chr); extern const char *mystrerror(int err); extern void myperror(const char *emsg); extern int mygetmsg(int fd, struct strbuf *ctrl, struct strbuf *data, int *flags); /* PPPoE-specific functions. */ extern poep_t *poe_mkheader(void *dptr, uint8_t codeval, int sessionid); extern boolean_t poe_tagcheck(const poep_t *poep, int length, const uint8_t *tptr); extern int poe_add_str(poep_t *poep, uint16_t ttype, const char *str); extern int poe_add_long(poep_t *poep, uint16_t ttype, uint32_t val); extern int poe_two_longs(poep_t *poep, uint16_t ttype, uint32_t val1, uint32_t val2); extern int poe_tag_copy(poep_t *poep, const uint8_t *tagp); extern const char *poe_tagname(uint16_t tagtype); extern const char *poe_codename(uint8_t codetype); /* These are here in case access wrappers are desired. */ #define poe_version_type(p) ((p)->poep_version_type) #define poe_code(p) ((p)->poep_code) #define poe_session_id(p) ntohs((p)->poep_session_id) #define poe_length(p) ntohs((p)->poep_length) #ifdef __cplusplus } #endif #endif /* PPPOE_COMMON_H */ /* * 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 */ /* * PPPoE Server-mode daemon log file support. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "logging.h" /* Not all functions are used by all applications. Let lint know this. */ /*LINTLIBRARY*/ const char *prog_name = "none"; /* Subsystem name for syslog */ int log_level; /* Higher number for more detail. */ static int curlogfd = -1; /* Current log file */ static const char *curfname; /* Name of current log file */ static const char *stderr_name = "stderr"; #define SMALLSTR 254 /* Don't allocate for most strings. */ /* * Returns -1 on error (with errno set), 0 on blocked write (file * system full), or N (buffer length) on success. */ static int dowrite(int fd, const void *buf, int len) { int retv; const uint8_t *bp = (uint8_t *)buf; while (len > 0) { retv = write(fd, bp, len); if (retv == 0) { break; } if (retv == -1) { if (errno != EINTR) break; } else { bp += retv; len -= retv; } } if (len <= 0) return (bp - (uint8_t *)buf); return (retv); } /* A close that avoids closing stderr */ static int doclose(void) { int retval = 0; if (curlogfd == -1) return (0); if ((curlogfd != STDERR_FILENO) || (curfname != stderr_name)) retval = close(curlogfd); curlogfd = -1; return (retval); } /* * Log levels are 0 for no messages, 1 for errors, 2 for warnings, 3 * for informational messages, and 4 for debugging messages. */ static void vlogat(int loglev, const char *fmt, va_list args) { char timbuf[64]; char regbuf[SMALLSTR+2]; char *ostr; int timlen; int slen; char *nstr; int err1, err2; int sloglev; int retv; va_list args2; static int xlate_loglev[] = { LOG_ERR, LOG_WARNING, LOG_INFO, LOG_DEBUG }; if (loglev >= log_level) return; timbuf[0] = '\0'; timlen = 0; if (curlogfd >= 0) { time_t now = time(NULL); /* * Form a time/date string for file (non-syslog) logging. * Caution: string broken in two so that SCCS doesn't mangle * the %-T-% sequence. */ timlen = strftime(timbuf, sizeof (timbuf), "%Y/%m/%d %T" "%Z: ", localtime(&now)); } /* Try formatting once into the small buffer. */ va_copy(args2, args); slen = vsnprintf(regbuf, SMALLSTR, fmt, args); if (slen < SMALLSTR) { ostr = regbuf; } else { /* * Length returned by vsnprintf doesn't include null, * and may also be missing a terminating \n. */ ostr = alloca(slen + 2); slen = vsnprintf(ostr, slen + 1, fmt, args2); } /* Don't bother logging empty lines. */ if (slen <= 0) return; /* Tack on a \n if needed. */ if (ostr[slen - 1] != '\n') { ostr[slen++] = '\n'; ostr[slen] = '\0'; } /* Translate our log levels into syslog standard values */ assert(loglev >= 0 && loglev < Dim(xlate_loglev)); sloglev = xlate_loglev[loglev]; /* Log each line separately */ for (; *ostr != '\0'; ostr = nstr + 1) { nstr = strchr(ostr, '\n'); /* Ignore zero-length lines. */ if (nstr == ostr) continue; slen = nstr - ostr + 1; /* * If we're supposed to be logging to a file, then try * that first. Ditch the file and revert to syslog if * any errors occur. */ if (curlogfd >= 0) { if ((retv = dowrite(curlogfd, timbuf, timlen)) > 0) retv = dowrite(curlogfd, ostr, slen); /* * If we've successfully logged this line, * then go do the next one. */ if (retv > 0) continue; /* Save errno (if any) and close log file */ err1 = errno; if (doclose() == -1) err2 = errno; else err2 = 0; /* * Recursion is safe here because we cleared * out curlogfd above. */ if (retv == -1) logerr("write log %s: %s", curfname, mystrerror(err1)); else logerr("cannot write %s", curfname); if (err2 == 0) logdbg("closed log %s", curfname); else logerr("closing log %s: %s", curfname, mystrerror(err2)); } syslog(sloglev, "%.*s", slen, ostr); } } /* Log at debug level */ void logdbg(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_DBG, fmt, args); va_end(args); } /* Log informational messages */ void loginfo(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_INFO, fmt, args); va_end(args); } /* Log warning messages */ void logwarn(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_WARN, fmt, args); va_end(args); } /* Log error messages */ void logerr(const char *fmt, ...) { va_list args; va_start(args, fmt); vlogat(LOGLVL_ERR, fmt, args); va_end(args); } /* Log a strerror message */ void logstrerror(const char *emsg) { logerr("%s: %s\n", emsg, mystrerror(errno)); } void log_to_stderr(int dbglvl) { log_level = dbglvl; if (curlogfd >= 0) close_log_files(); curlogfd = STDERR_FILENO; curfname = stderr_name; } /* * Set indicated log file and debug level. */ void log_for_service(const char *fname, int dbglvl) { int err1, err2; boolean_t closed; log_level = dbglvl; if (fname != NULL && (*fname == '\0' || strcasecmp(fname, "syslog") == 0)) fname = NULL; if (fname == NULL && curfname == NULL) return; err1 = err2 = 0; closed = B_FALSE; if (curlogfd >= 0) { if (fname == curfname || (fname != NULL && strcmp(fname, curfname) == 0)) { curfname = fname; return; } if (doclose() == -1) err1 = errno; closed = B_TRUE; } if (fname != NULL) { curlogfd = open(fname, O_WRONLY|O_APPEND|O_CREAT, 0600); if (curlogfd == -1) err2 = errno; } if (closed) { if (err1 == 0) logdbg("closed log %s", curfname); else logerr("closing log %s: %s", curfname, mystrerror(err1)); } if (fname != NULL) { if (err2 == 0) logdbg("opened log %s", fname); else logerr("opening log %s: %s", fname, mystrerror(err2)); } curfname = fname; } /* * Close any open log file. This is used for SIGHUP (to support log * file rotation) and when execing. */ void close_log_files(void) { int err = 0; if (curlogfd >= 0) { if (doclose() == -1) err = errno; if (err == 0) logdbg("closed log %s", curfname); else logerr("closing log %s: %s", curfname, mystrerror(err)); } } /* * Reopen syslog connection; in case it was closed. */ void reopen_log(void) { openlog(prog_name, LOG_PID | LOG_NDELAY | LOG_NOWAIT, LOG_DAEMON); /* I control the log level */ (void) setlogmask(LOG_UPTO(LOG_DEBUG)); } /* * 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 */ /* * PPPoE Server-mode daemon logging functions. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef PPPOE_LOGGING_H #define PPPOE_LOGGING_H #ifdef __cplusplus extern "C" { #endif #define LOGLVL_DBG 3 #define LOGLVL_INFO 2 #define LOGLVL_WARN 1 #define LOGLVL_ERR 0 /* Functions in logging.c */ extern void logdbg(const char *fmt, ...); extern void loginfo(const char *fmt, ...); extern void logwarn(const char *fmt, ...); extern void logerr(const char *fmt, ...); extern void logstrerror(const char *emsg); extern void log_for_service(const char *fname, int dbglvl); extern void log_to_stderr(int dbglvl); extern void close_log_files(void); extern void reopen_log(void); /* Data in logging.c */ extern const char *prog_name; extern int log_level; /* Functions in options.c */ extern void global_logging(void); /* A handy macro. */ #ifndef Dim #define Dim(x) (sizeof (x) / sizeof (*(x))) #endif #ifdef __cplusplus } #endif #endif /* PPPOE_LOGGING_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 */ /* * PPPoE Server-mode daemon option parsing. * * Copyright 2009 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. * Copyright (c) 2016 by Delphix. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "common.h" #include "logging.h" #define MAX_KEYWORD 4096 /* Maximum token length */ #define MAX_NEST 32 /* Maximum ${$sub} nesting */ #define MAXARGS 256 /* Maximum number of pppd arguments */ /* * Client filter entry. These are linked in *reverse* order so that * the DAG created by file inclusion nesting works as expected. Since * the administrator who wrote the configuration expects "first * match," this means that tests against the filter list must actually * use "last match." */ struct filter_entry { struct filter_entry *fe_prev; /* Previous filter in list */ struct ether_addr fe_mac; /* MAC address */ struct ether_addr fe_mask; /* Mask for above address test */ uchar_t fe_isexcept; /* invert sense; exclude matching clients */ uchar_t fe_prevcopy; /* fe_prev points to copied list */ uchar_t fe_unused[2]; /* padding */ }; /* * Note: I would like to make the strings and filters here const, but * I can't because they have to be passed to free() during parsing. I * could work around this with offsetof() or data copies, but it's not * worth the effort. */ struct service_entry { const char *se_name; /* Name of service */ struct filter_entry *se_flist; /* Pointer to list of client filters */ uint_t se_flags; /* SEF_* flags (below) */ int se_debug; /* Debug level (0=nodebug) */ char *se_server; /* Server (AC) name */ char *se_pppd; /* Options for pppd */ char *se_path; /* Path to pppd executable */ char *se_extra; /* Extra options */ char *se_log; /* Log file */ uid_t se_uid; /* User ID */ gid_t se_gid; /* Group ID */ }; #define SEF_WILD 0x00000001 /* Offer in wildcard reply */ #define SEF_NOWILD 0x00000002 /* Don't offer in wildcard */ #define SEF_CFLIST 0x00000004 /* se_flist copied from global */ #define SEF_CSERVER 0x00000008 /* se_server copied from global */ #define SEF_CPPPD 0x00000010 /* se_pppd copied from global */ #define SEF_CPATH 0x00000020 /* se_path copied from global */ #define SEF_CEXTRA 0x00000040 /* se_extra copied from global */ #define SEF_CLOG 0x00000080 /* se_log copied from global */ #define SEF_UIDSET 0x00000100 /* se_uid has been set */ #define SEF_GIDSET 0x00000200 /* se_gid has been set */ #define SEF_DEBUGCLR 0x00000400 /* do not add se_debug from global */ #define SEF_CDEV 0x00000800 /* copied devs (parse only) */ /* * One of these is allocated per lower-level stream (device) that is * referenced by the configuration files. The queries are received * per device, and this structure allows us to find all of the * services that correspond to that device. */ struct device_entry { const char *de_name; const struct service_entry **de_services; int de_nservices; }; /* * This is the parsed configuration. While a new configuration is * being read, this is kept around until the new configuration is * ready, and then it is discarded in one operation. It has an array * of device entries (as above) -- one per referenced lower stream -- * and a pointer to the allocated parser information. The latter is * kept around because we reuse pointers rather than reallocating and * copying the data. There are thus multiple aliases to the dynamic * data, and the "owner" (for purposes of freeing the storage) is * considered to be this 'junk' list. */ struct option_state { const struct device_entry *os_devices; int os_ndevices; struct per_file *os_pfjunk; /* Kept for deallocation */ char **os_evjunk; /* ditto */ }; /* * This is the root pointer to the current parsed options. * This cannot be const because it's passed to free() when reparsing * options. */ static struct option_state *cur_options; /* Global settings for module-wide options. */ static struct service_entry glob_svc; /* * ******************************************************************* * Data structures generated during parsing. */ /* List of device names attached to one service */ struct device_list { struct device_list *dl_next; const char *dl_name; /* Name of one device */ }; /* Entry for a single defined service. */ struct service_list { struct service_entry sl_entry; /* Parsed service data */ struct service_list *sl_next; /* Next service entry */ struct parse_state *sl_parse; /* Back pointer to state */ struct device_list *sl_dev; /* List of devices */ int sl_serial; /* Serial number (conflict resolve) */ }; #define SESERIAL(x) ((struct service_list *)&(x))->sl_serial #define ISGLOBAL(x) ((x) == &(x)->sl_parse->ps_cfile->pf_global) /* * Structure allocated for each file opened. File nesting is chained * in reverse order so that global option scoping works as expected. */ struct per_file { struct per_file *pf_prev; /* Back chain */ struct service_list pf_global; /* Global (default) service context */ struct service_list *pf_svc; /* List of services */ struct service_list *pf_svc_last; FILE *pf_input; /* File for input */ const char *pf_name; /* File name */ int pf_nsvc; /* Count of services */ }; /* State of parser */ enum key_state { ksDefault, ksService, ksDevice, ksClient, ksClientE, ksServer, ksPppd, ksFile, ksPath, ksExtra, ksLog, ksUser, ksGroup }; /* * Global parser state. There is one of these structures, and it * exists only while actively parsing configuration files. */ struct parse_state { enum key_state ps_state; /* Parser state */ int ps_serial; /* Service serial number */ struct per_file *ps_files; /* Parsed files */ struct per_file *ps_cfile; /* Current file */ struct service_list *ps_csvc; /* Current service */ struct device_list *ps_star; /* Wildcard device */ int ps_flags; /* PSF_* below */ char **ps_evlist; /* allocated environment variables */ int ps_evsize; /* max length; for realloc */ }; #define PSF_PERDEV 0x0001 /* In a per-device file */ #define PSF_SETLEVEL 0x0002 /* Set log level along the way */ /* Should be in a library somewhere. */ static char * strsave(const char *str) { char *newstr; if (str == NULL) return (NULL); newstr = (char *)malloc(strlen(str) + 1); if (newstr != NULL) (void) strcpy(newstr, str); return (newstr); } /* * Stop defining current service and revert to global definition. * This resolves any implicit references to global options by copying * ("inheriting") from the current global state. */ static void close_service(struct service_list *slp) { struct parse_state *psp; struct per_file *cfile; struct service_entry *sep; struct service_entry *sedefp; struct filter_entry *fep; assert(slp != NULL); psp = slp->sl_parse; cfile = psp->ps_cfile; /* If no current file, then nothing to close. */ if (cfile == NULL) return; sep = &slp->sl_entry; /* * Fix up filter pointers to make DAG. First, locate * the end of the filter list. */ if (sep->se_flags & SEF_CFLIST) { sep->se_flist = fep = NULL; } else { for (fep = sep->se_flist; fep != NULL; fep = fep->fe_prev) if (fep->fe_prev == NULL || fep->fe_prevcopy) { fep->fe_prev = NULL; break; } } if (slp == &cfile->pf_global) { /* * If we're in a global context, then we're about to * open a new service, so it's time to fix up the * filter list so that it's usable as a reference. * Loop through files from which we were included, and * link up filters. Note: closure may occur more than * once here. */ /* We don't inherit from ourselves. */ cfile = cfile->pf_prev; while (cfile != NULL) { if (fep == NULL) { sep->se_flist = fep = cfile->pf_global.sl_entry.se_flist; sep->se_flags |= SEF_CFLIST; } else if (fep->fe_prev == NULL) { fep->fe_prev = cfile->pf_global.sl_entry.se_flist; fep->fe_prevcopy = 1; } cfile = cfile->pf_prev; } } else { /* * Loop through default options in current and all * enclosing include files. Inherit options. */ logdbg("service %s ends", slp->sl_entry.se_name); while (cfile != NULL) { /* Inherit from global service options. */ if (slp->sl_dev == NULL) { slp->sl_dev = cfile->pf_global.sl_dev; sep->se_flags |= SEF_CDEV; } sedefp = &cfile->pf_global.sl_entry; if (fep == NULL) { sep->se_flist = fep = sedefp->se_flist; sep->se_flags |= SEF_CFLIST; } else if (fep->fe_prev == NULL) { fep->fe_prev = sedefp->se_flist; fep->fe_prevcopy = 1; } if (sep->se_server == NULL) { sep->se_server = sedefp->se_server; sep->se_flags |= SEF_CSERVER; } if (sep->se_pppd == NULL) { sep->se_pppd = sedefp->se_pppd; sep->se_flags |= SEF_CPPPD; } if (sep->se_path == NULL) { sep->se_path = sedefp->se_path; sep->se_flags |= SEF_CPATH; } if (sep->se_extra == NULL) { sep->se_extra = sedefp->se_extra; sep->se_flags |= SEF_CEXTRA; } if (sep->se_log == NULL) { sep->se_log = sedefp->se_log; sep->se_flags |= SEF_CLOG; } if (!(sep->se_flags & SEF_UIDSET) && (sedefp->se_flags & SEF_UIDSET)) { sep->se_uid = sedefp->se_uid; sep->se_flags |= SEF_UIDSET; } if (!(sep->se_flags & SEF_GIDSET) && (sedefp->se_flags & SEF_GIDSET)) { sep->se_gid = sedefp->se_gid; sep->se_flags |= SEF_GIDSET; } if (!(sep->se_flags & (SEF_WILD|SEF_NOWILD))) sep->se_flags |= sedefp->se_flags & (SEF_WILD|SEF_NOWILD); if (!(sep->se_flags & SEF_DEBUGCLR)) { sep->se_debug += sedefp->se_debug; sep->se_flags |= sedefp->se_flags & SEF_DEBUGCLR; } cfile = cfile->pf_prev; } } /* Revert to global definitions. */ psp->ps_csvc = &psp->ps_cfile->pf_global; } /* Discard a dynamic device list */ static void free_device_list(struct device_list *dlp) { struct device_list *dln; while (dlp != NULL) { dln = dlp->dl_next; free(dlp); dlp = dln; } } /* * Handle "service " -- finish up previous service definition * (if any) by copying from global state where necessary, and start * defining new service. */ static int set_service(struct service_list *slp, const char *str) { struct parse_state *psp; struct per_file *cfile; /* Finish current service */ close_service(slp); /* Start new service */ psp = slp->sl_parse; slp = (struct service_list *)calloc(sizeof (*slp) + strlen(str) + 1, 1); if (slp == NULL) { logerr("no memory for service \"%s\"", str); return (-1); } /* Add to end of list */ cfile = psp->ps_cfile; if (cfile->pf_svc_last == NULL) cfile->pf_svc = slp; else cfile->pf_svc_last->sl_next = slp; cfile->pf_svc_last = slp; cfile->pf_nsvc++; /* Fill in initial service entry */ slp->sl_entry.se_name = (const char *)(slp+1); (void) strcpy((char *)(slp+1), str); logdbg("service %s begins", slp->sl_entry.se_name); slp->sl_serial = psp->ps_serial++; slp->sl_parse = psp; /* This is now the current service that we're defining. */ psp->ps_csvc = slp; return (0); } /* * Handle both "wildcard" and "nowildcard" options. */ static int set_wildcard(struct service_list *slp, const char *str) { /* Allow global context to switch back and forth without error. */ if (!ISGLOBAL(slp) && (slp->sl_entry.se_flags & (SEF_WILD|SEF_NOWILD))) { logdbg("%s: extra \"%s\" ignored", slp->sl_parse->ps_cfile->pf_name, str); return (0); } slp->sl_entry.se_flags = (slp->sl_entry.se_flags & ~(SEF_WILD|SEF_NOWILD)) | (*str == 'n' ? SEF_NOWILD : SEF_WILD); return (0); } /* * Handle "debug" option. */ /*ARGSUSED*/ static int set_debug(struct service_list *slp, const char *str) { slp->sl_entry.se_debug++; if (ISGLOBAL(slp) && (slp->sl_parse->ps_flags & PSF_SETLEVEL)) { log_level = slp->sl_entry.se_debug; } return (0); } /* * Handle "nodebug" option. */ /*ARGSUSED*/ static int set_nodebug(struct service_list *slp, const char *str) { slp->sl_entry.se_flags |= SEF_DEBUGCLR; slp->sl_entry.se_debug = 0; if (ISGLOBAL(slp) && (slp->sl_parse->ps_flags & PSF_SETLEVEL)) { log_level = slp->sl_entry.se_debug; } return (0); } /* * Handle all plain string options; "server", "pppd", "path", "extra", * and "log". */ static int set_string(struct service_list *slp, const char *str) { char **cpp; assert(!(slp->sl_entry.se_flags & (SEF_CSERVER|SEF_CPPPD|SEF_CPATH|SEF_CEXTRA|SEF_CLOG))); switch (slp->sl_parse->ps_state) { case ksServer: cpp = &slp->sl_entry.se_server; break; case ksPppd: cpp = &slp->sl_entry.se_pppd; break; case ksPath: cpp = &slp->sl_entry.se_path; break; case ksExtra: cpp = &slp->sl_entry.se_extra; break; case ksLog: cpp = &slp->sl_entry.se_log; break; default: assert(0); return (-1); } if (*cpp != NULL) free(*cpp); *cpp = strsave(str); return (0); } /* * Handle "file " option. Close out current service (if any) * and begin parsing from new file. */ static int set_file(struct service_list *slp, const char *str) { FILE *fp; struct per_file *pfp; struct parse_state *psp; close_service(slp); if ((fp = fopen(str, "r")) == NULL) { logwarn("%s: %s: %s", slp->sl_parse->ps_cfile->pf_name, str, mystrerror(errno)); return (-1); } pfp = (struct per_file *)calloc(sizeof (*pfp) + strlen(str) + 1, 1); if (pfp == NULL) { logerr("no memory for parsing file %s", str); (void) fclose(fp); return (-1); } logdbg("config file %s open", str); /* Fill in new file structure. */ pfp->pf_name = (const char *)(pfp+1); (void) strcpy((char *)(pfp+1), str); pfp->pf_input = fp; psp = slp->sl_parse; pfp->pf_prev = psp->ps_cfile; psp->ps_cfile = pfp; /* Start off in global context for this file. */ psp->ps_csvc = &pfp->pf_global; pfp->pf_global.sl_parse = psp; pfp->pf_global.sl_entry.se_name = ""; return (0); } /* * Handle "device " option. */ static int set_device(struct service_list *slp, const char *str) { struct parse_state *psp = slp->sl_parse; struct device_list *dlp; struct device_list *dln; struct device_list **dlpp; const char *cp; int len; /* Can't use this option in the per-device files. */ if (psp->ps_flags & PSF_PERDEV) { logerr("\"device %s\" ignored in %s", str, psp->ps_cfile->pf_name); return (0); } if (strcmp(str, "*") == 0 || strcmp(str, "all") == 0) { if (!(slp->sl_entry.se_flags & SEF_CDEV)) free_device_list(slp->sl_dev); slp->sl_dev = psp->ps_star; slp->sl_entry.se_flags |= SEF_CDEV; } else { dlpp = &dlp; for (;;) { while (isspace(*str) || *str == ',') str++; if (*str == '\0') break; cp = str; while (*str != '\0' && !isspace(*str) && *str != ',') str++; len = str - cp; if ((len == 1 && *cp == '*') || (len == 3 && strncmp(cp, "all", 3) == 0)) { logerr("%s: cannot use %.*s in device list", psp->ps_cfile->pf_name, len, cp); continue; } dln = (struct device_list *)malloc(sizeof (*dln) + len + 1); if (dln == NULL) { logerr("no memory for device name"); break; } dln->dl_name = (const char *)(dln + 1); /* Cannot use strcpy because cp isn't terminated. */ (void) memcpy(dln + 1, cp, len); ((char *)(dln + 1))[len] = '\0'; logdbg("%s: device %s", psp->ps_cfile->pf_name, dln->dl_name); *dlpp = dln; dlpp = &dln->dl_next; } *dlpp = NULL; dlpp = &slp->sl_dev; if (!(slp->sl_entry.se_flags & SEF_CDEV)) while (*dlpp != NULL) dlpp = &(*dlpp)->dl_next; *dlpp = dlp; slp->sl_entry.se_flags &= ~SEF_CDEV; } return (0); } /* * Handle portion of "client [except] " option. Attach * to list of filters in reverse order. */ static int set_client(struct service_list *slp, const char *str) { struct parse_state *psp = slp->sl_parse; struct filter_entry *fep; struct filter_entry *fen; const char *cp; int len; char hbuf[MAXHOSTNAMELEN]; struct ether_addr ea; struct ether_addr mask; uchar_t *ucp; uchar_t *mcp; /* Head of list. */ fep = slp->sl_entry.se_flist; for (;;) { while (isspace(*str) || *str == ',') str++; if (*str == '\0') break; cp = str; while (*str != '\0' && !isspace(*str) && *str != ',') str++; len = str - cp; (void) memcpy(hbuf, cp, len); hbuf[len] = '\0'; mcp = mask.ether_addr_octet; mcp[0] = mcp[1] = mcp[2] = mcp[3] = mcp[4] = mcp[5] = 0xFF; if (ether_hostton(hbuf, &ea) != 0) { ucp = ea.ether_addr_octet; while (cp < str) { if (ucp >= ea.ether_addr_octet + sizeof (ea)) break; if (*cp == '*') { *mcp++ = *ucp++ = 0; cp++; } else { if (!isxdigit(*cp)) break; *ucp = hexdecode(*cp++); if (cp < str && isxdigit(*cp)) { *ucp = (*ucp << 4) | hexdecode(*cp++); } ucp++; *mcp++ = 0xFF; } if (cp < str) { if (*cp != ':' || cp + 1 == str) break; cp++; } } if (cp < str) { logerr("%s: illegal Ethernet address %.*s", psp->ps_cfile->pf_name, len, cp); continue; } } fen = (struct filter_entry *)malloc(sizeof (*fen)); if (fen == NULL) { logerr("unable to allocate memory for filter"); break; } fen->fe_isexcept = psp->ps_state == ksClientE; fen->fe_prevcopy = 0; (void) memcpy(&fen->fe_mac, &ea, sizeof (fen->fe_mac)); (void) memcpy(&fen->fe_mask, &mask, sizeof (fen->fe_mask)); fen->fe_prev = fep; fep = fen; } slp->sl_entry.se_flist = fep; return (0); } /* * Handle "user " option. */ static int set_user(struct service_list *slp, const char *str) { struct passwd *pw; char *cp; uid_t myuid, uid; if ((pw = getpwnam(str)) == NULL) { uid = (uid_t)strtol(str, &cp, 0); if (str == cp || *cp != '\0') { logerr("%s: bad user name \"%s\"", slp->sl_parse->ps_cfile->pf_name, str); return (0); } } else { uid = pw->pw_uid; } slp->sl_entry.se_uid = uid; myuid = getuid(); if (myuid != 0) { if (myuid == uid) return (0); logdbg("%s: not root; ignoring attempt to set UID %d (%s)", slp->sl_parse->ps_cfile->pf_name, uid, str); return (0); } slp->sl_entry.se_flags |= SEF_UIDSET; return (0); } /* * Handle "group " option. */ static int set_group(struct service_list *slp, const char *str) { struct group *gr; char *cp; gid_t gid; if ((gr = getgrnam(str)) == NULL) { gid = (gid_t)strtol(str, &cp, 0); if (str == cp || *cp != '\0') { logerr("%s: bad group name \"%s\"", slp->sl_parse->ps_cfile->pf_name, str); return (0); } } else { gid = gr->gr_gid; } slp->sl_entry.se_gid = gid; if (getuid() != 0) { logdbg("%s: not root; ignoring attempt to set GID %d (%s)", slp->sl_parse->ps_cfile->pf_name, gid, str); return (0); } slp->sl_entry.se_flags |= SEF_GIDSET; return (0); } /* * This state machine is used to parse the configuration files. The * "kwe_in" is the state in which the keyword is recognized. The * "kwe_out" is the state that the keyword produces. */ struct kw_entry { const char *kwe_word; enum key_state kwe_in; enum key_state kwe_out; int (*kwe_func)(struct service_list *slp, const char *str); }; static const struct kw_entry key_list[] = { { "service", ksDefault, ksService, NULL }, { "device", ksDefault, ksDevice, NULL }, { "client", ksDefault, ksClient, NULL }, { "except", ksClient, ksClientE, NULL }, { "wildcard", ksDefault, ksDefault, set_wildcard }, { "nowildcard", ksDefault, ksDefault, set_wildcard }, { "server", ksDefault, ksServer, NULL }, { "pppd", ksDefault, ksPppd, NULL }, { "debug", ksDefault, ksDefault, set_debug }, { "nodebug", ksDefault, ksDefault, set_nodebug }, { "file", ksDefault, ksFile, NULL }, { "path", ksDefault, ksPath, NULL }, { "extra", ksDefault, ksExtra, NULL }, { "log", ksDefault, ksLog, NULL }, { "user", ksDefault, ksUser, NULL }, { "group", ksDefault, ksGroup, NULL }, /* Wildcards only past this point. */ { "", ksService, ksDefault, set_service }, { "", ksDevice, ksDefault, set_device }, { "", ksClient, ksDefault, set_client }, { "", ksClientE, ksDefault, set_client }, { "", ksServer, ksDefault, set_string }, { "", ksPppd, ksDefault, set_string }, { "", ksFile, ksDefault, set_file }, { "", ksPath, ksDefault, set_string }, { "", ksExtra, ksDefault, set_string }, { "", ksLog, ksDefault, set_string }, { "", ksUser, ksDefault, set_user }, { "", ksGroup, ksDefault, set_group }, { NULL, ksDefault, ksDefault, NULL } }; /* * Produce a string for the keyword that would have gotten us into the * current state. */ static const char * after_key(enum key_state kstate) { const struct kw_entry *kep; for (kep = key_list; kep->kwe_word != NULL; kep++) if (kep->kwe_out == kstate) return (kep->kwe_word); return ("nothing"); } /* * Handle end-of-file processing -- close service, close file, revert * to global context in previous include file nest level. */ static void file_end(struct parse_state *psp) { struct per_file *pfp; /* Must not be in the middle of parsing a multi-word sequence now. */ if (psp->ps_state != ksDefault) { logerr("%s ends with \"%s\"", psp->ps_cfile->pf_name, after_key(psp->ps_state)); psp->ps_state = ksDefault; } close_service(psp->ps_csvc); if ((pfp = psp->ps_cfile) != NULL) { /* Put this file on the list of finished files. */ psp->ps_cfile = pfp->pf_prev; pfp->pf_prev = psp->ps_files; psp->ps_files = pfp; if (pfp->pf_input != NULL) { logdbg("file %s closed", pfp->pf_name); (void) fclose(pfp->pf_input); pfp->pf_input = NULL; } /* Back up to previous file, if any, and set global context. */ if ((pfp = psp->ps_cfile) != NULL) psp->ps_csvc = &pfp->pf_global; } } /* * Dispatch a single keyword against the parser state machine or * handle an environment variable assignment. The input is a string * containing the single word to be dispatched. */ static int dispatch_keyword(struct parse_state *psp, const char *keybuf) { const struct kw_entry *kep; int retv; char *cp; char *env; char **evlist; int len; retv = 0; for (kep = key_list; kep->kwe_word != NULL; kep++) { if (kep->kwe_in == psp->ps_state && (*kep->kwe_word == '\0' || strcasecmp(kep->kwe_word, keybuf) == 0)) { if (kep->kwe_func != NULL) retv = (*kep->kwe_func)(psp->ps_csvc, keybuf); psp->ps_state = kep->kwe_out; return (retv); } } if (strchr(keybuf, '=') != NULL) { if ((cp = strsave(keybuf)) == NULL) { logerr("no memory to save %s", keybuf); return (0); } len = (strchr(cp, '=') - cp) + 1; if ((evlist = psp->ps_evlist) == NULL) { psp->ps_evlist = evlist = (char **)malloc(8 * sizeof (*evlist)); if (evlist == NULL) { logerr("no memory for evlist"); free(cp); return (0); } psp->ps_evsize = 8; evlist[0] = evlist[1] = NULL; } else { while ((env = *evlist) != NULL) { if (strncmp(cp, env, len) == 0) break; evlist++; } if (env == NULL && evlist-psp->ps_evlist >= psp->ps_evsize-1) { evlist = (char **)realloc(psp->ps_evlist, (psp->ps_evsize + 8) * sizeof (*evlist)); if (evlist == NULL) { logerr("cannot realloc evlist to %d", psp->ps_evsize + 8); free(cp); return (0); } psp->ps_evlist = evlist; evlist += psp->ps_evsize - 1; psp->ps_evsize += 8; evlist[1] = NULL; } } logdbg("setenv \"%s\"", cp); if (*evlist != NULL) free(*evlist); *evlist = cp; return (0); } logerr("%s: unknown keyword '%s'", psp->ps_cfile->pf_name, keybuf); return (-1); } /* * Modified version of standard getenv; looks in locally-stored * environment first. This function exists because we need to be able * to revert to the original environment during a reread (SIGHUP), and * the putenv() function overwrites that environment. */ static char * my_getenv(struct parse_state *psp, char *estr) { char **evlist, *ent; int elen; if (psp != NULL && (evlist = psp->ps_evlist) != NULL) { elen = strlen(estr); while ((ent = *evlist++) != NULL) { if (strncmp(ent, estr, elen) == 0 && ent[elen] == '=') return (ent + elen + 1); } } return (getenv(estr)); } /* * Expand an environment variable at the end of current buffer and * return pointer to next spot in buffer for character append. psp * context may be null. */ static char * env_replace(struct parse_state *psp, char *keybuf, char kwstate) { char *cpe; char *cp; if ((cp = strrchr(keybuf, kwstate)) != NULL) { if ((cpe = my_getenv(psp, cp + 1)) != NULL) { *cp = '\0'; (void) strncat(cp, cpe, MAX_KEYWORD - (cp - keybuf) - 1); keybuf[MAX_KEYWORD - 1] = '\0'; cp += strlen(cp); } else { logerr("unknown variable \"%s\"", cp + 1); } } else { /* Should not occur. */ cp = keybuf + strlen(keybuf); } return (cp); } /* * Given a character-at-a-time input function, get a delimited keyword * from the input. This function handles the usual escape sequences, * quoting, commenting, and environment variable expansion. * * The standard wordexp(3C) function isn't used here because the POSIX * definition is hard to use, and the Solaris implementation is * resource-intensive and insecure. The "hard-to-use" part is that * wordexp expands only variables from the environment, and can't * handle an environment overlay. Instead, the caller must use the * feeble putenv/getenv interface, and rewinding to the initial * environment without leaking storage is hard. The Solaris * implementation invokes an undocumented extensions via * fork/exec("/bin/ksh -\005 %s") for every invocation, and gathers * the expanded result with pipe. This makes it slow to execute and * exposes the string being expanded to users with access to "ps -f." * * psp may be null; it's used only for environment variable expansion. * Input "flag" is 1 to ignore EOL, '#', and '$'; 0 for normal file parsing. * * Returns: * 0 - keyword parsed. * 1 - end of file; no keyword. * 2 - end of file after this keyword. */ static int getkeyword(struct parse_state *psp, char *keybuf, int keymax, int (*nextchr)(void *), void *arg, int flag) { char varnest[MAX_NEST]; char *kbp; char *vnp; char chr; int ichr; char kwstate; static const char escstr[] = "a\ab\bf\fn\nr\r"; const char *cp; keymax--; /* Account for trailing NUL byte */ kwstate = '\0'; kbp = keybuf; vnp = varnest; for (;;) { ichr = (*nextchr)(arg); chr = (char)ichr; tryagain: switch (kwstate) { case '\\': /* Start of unquoted escape sequence */ case '|': /* Start of escape sequence in double quotes */ case '~': /* Start of escape sequence in single quotes */ /* Convert the character if we can. */ if (chr == '\n') chr = '\0'; else if (isalpha(chr) && (cp = strchr(escstr, chr)) != NULL) chr = cp[1]; /* Revert to previous state */ switch (kwstate) { case '\\': kwstate = 'A'; break; case '|': kwstate = '"'; break; case '~': kwstate = '\''; break; } break; case '"': /* In double-quote string */ if (!flag && chr == '$') { /* Handle variable expansion. */ kwstate = '%'; chr = '\0'; break; } /* FALLTHROUGH */ case '\'': /* In single-quote string */ if (chr == '\\') { /* Handle start of escape sequence */ kwstate = kwstate == '"' ? '|' : '~'; chr = '\0'; break; } if (chr == kwstate) { /* End of quoted string; revert to normal */ kwstate = 'A'; chr = '\0'; } break; case '$': /* Start of unquoted variable name */ case '%': /* Start of variable name in quoted string */ if (chr == '{') { /* Variable name is bracketed. */ kwstate = chr = kwstate == '$' ? '{' : '['; break; } *kbp++ = kwstate = kwstate == '$' ? '+' : '*'; /* FALLTHROUGH */ case '+': /* Gathering unquoted variable name */ case '*': /* Gathering variable name in quoted string */ if (chr == '$' && vnp < varnest + sizeof (varnest)) { *vnp++ = kwstate; kwstate = '$'; chr = '\0'; break; } if (!isalnum(chr) && chr != '_' && chr != '.' && chr != '-') { *kbp = '\0'; kbp = env_replace(psp, keybuf, kwstate); if (vnp > varnest) kwstate = *--vnp; else kwstate = kwstate == '+' ? 'A' : '"'; /* Go reinterpret in new context */ goto tryagain; } break; case '{': /* Gathering bracketed, unquoted var name */ case '[': /* Gathering bracketed, quoted var name */ if (chr == '}') { *kbp = '\0'; kbp = env_replace(psp, keybuf, kwstate); kwstate = kwstate == '{' ? 'A' : '"'; chr = '\0'; } break; case '#': /* Comment before word state */ case '@': /* Comment after word state */ if (chr == '\n' || chr == '\r' || ichr == EOF) { /* At end of line, revert to previous state */ kwstate = kwstate == '#' ? '\0' : ' '; chr = '\0'; break; } chr = '\0'; break; case '\0': /* Initial state; no word seen yet. */ if (ichr == EOF || isspace(chr)) { chr = '\0'; /* Skip over leading spaces */ break; } if (chr == '#') { kwstate = '#'; chr = '\0'; /* Skip over comments */ break; } /* Start of keyword seen. */ kwstate = 'A'; /* FALLTHROUGH */ default: /* Middle of keyword parsing. */ if (ichr == EOF) break; if (isspace(chr)) { /* Space terminates word */ kwstate = ' '; break; } if (chr == '"' || chr == '\'' || chr == '\\') { kwstate = chr; /* Begin quote or escape */ chr = '\0'; break; } if (flag) /* Allow ignore; for string reparse */ break; if (chr == '#') { /* Comment terminates word */ kwstate = '@'; /* Must consume comment also */ chr = '\0'; break; } if (chr == '$') { kwstate = '$'; /* Begin variable expansion */ chr = '\0'; } break; } /* * If we've reached a space at the end of the word, * then we're done. */ if (ichr == EOF || kwstate == ' ') break; /* * If there's a character to store and space * available, then add it to the string */ if (chr != '\0' && kbp < keybuf + keymax) *kbp++ = (char)chr; } *kbp = '\0'; if (ichr == EOF) { return (kwstate == '\0' ? 1 : 2); } return (0); } /* * Fetch words from current file until all files are closed. Handles * include files. */ static void parse_from_file(struct parse_state *psp) { char keybuf[MAX_KEYWORD]; int retv; while (psp->ps_cfile != NULL && psp->ps_cfile->pf_input != NULL) { retv = getkeyword(psp, keybuf, sizeof (keybuf), (int (*)(void *))fgetc, (void *)psp->ps_cfile->pf_input, 0); if (retv != 1) (void) dispatch_keyword(psp, keybuf); if (retv != 0) file_end(psp); } } /* * Open and parse named file. This is for the predefined * configuration files in /etc/ppp -- it's not an error if any of * these are missing. */ static void parse_file(struct parse_state *psp, const char *fname) { struct stat sb; /* It's ok if any of these files are missing. */ if (stat(fname, &sb) == -1 && errno == ENOENT) return; if (set_file(psp->ps_csvc, fname) == 0) parse_from_file(psp); } /* * Dispatch keywords from command line. Handles any files included * from there. */ static void parse_arg_list(struct parse_state *psp, int argc, char **argv) { /* The first argument (program name) can be null. */ if (--argc <= 0) return; while (--argc >= 0) { (void) dispatch_keyword(psp, *++argv); if (psp->ps_cfile->pf_input != NULL) parse_from_file(psp); } } /* Count length of dynamic device list */ static int count_devs(struct device_list *dlp) { int ndevs; ndevs = 0; for (; dlp != NULL; dlp = dlp->dl_next) ndevs++; return (ndevs); } /* Count number of devices named in entire file. */ static int count_per_file(struct per_file *pfp) { struct service_list *slp; int ndevs = 0; for (; pfp != NULL; pfp = pfp->pf_prev) { ndevs += count_devs(pfp->pf_global.sl_dev); for (slp = pfp->pf_svc; slp != NULL; slp = slp->sl_next) if (!(slp->sl_entry.se_flags & SEF_CDEV)) ndevs += count_devs(slp->sl_dev); } return (ndevs); } /* Write device names into linear array. */ static const char ** devs_to_list(struct device_list *dlp, const char **dnames) { for (; dlp != NULL; dlp = dlp->dl_next) *dnames++ = dlp->dl_name; return (dnames); } /* Write all device names from file into a linear array. */ static const char ** per_file_to_list(struct per_file *pfp, const char **dnames) { struct service_list *slp; for (; pfp != NULL; pfp = pfp->pf_prev) { dnames = devs_to_list(pfp->pf_global.sl_dev, dnames); for (slp = pfp->pf_svc; slp != NULL; slp = slp->sl_next) if (!(slp->sl_entry.se_flags & SEF_CDEV)) dnames = devs_to_list(slp->sl_dev, dnames); } return (dnames); } /* Compare device names; used with qsort */ static int devcmp(const void *d1, const void *d2) { return (strcmp(*(const char **)d1, *(const char **)d2)); } /* * Get sorted list of unique device names among all defined and * partially defined services in all files. */ static const char ** get_unique_devs(struct parse_state *psp) { int ndevs; const char **dnames; const char **dnp; const char **dnf; /* * Count number of explicitly referenced devices among all * services (including duplicates). */ ndevs = count_per_file(psp->ps_files); ndevs += count_per_file(psp->ps_cfile); if (ndevs <= 0) { return (NULL); } /* Sort and trim out duplicate devices. */ dnames = (const char **)malloc((ndevs+1) * sizeof (const char *)); if (dnames == NULL) { logerr("unable to allocate space for %d devices", ndevs + 1); return (NULL); } dnp = per_file_to_list(psp->ps_files, dnames); (void) per_file_to_list(psp->ps_cfile, dnp); qsort(dnames, ndevs, sizeof (const char *), devcmp); for (dnf = (dnp = dnames) + 1; dnf < dnames+ndevs; dnf++) if (strcmp(*dnf, *dnp) != 0) *++dnp = *dnf; *++dnp = NULL; /* Return array of pointers to names. */ return (dnames); } /* * Convert data structures created by parsing process into data * structures used by service dispatch. This gathers the unique * device (lower stream) names and attaches the services available on * each device to a list while triming duplicate services. */ static struct option_state * organize_state(struct parse_state *psp) { struct per_file *pfp; struct per_file *pftopp; struct service_list *slp; struct device_list *dlp; int ndevs; int nsvcs; const char **dnames; const char **dnp; struct device_entry *dep; struct option_state *osp; struct service_entry **sepp; struct service_entry **sebpp; struct service_entry **se2pp; /* * Parsing is now done. */ close_service(psp->ps_csvc); psp->ps_csvc = NULL; if ((pfp = psp->ps_cfile) != NULL) { pfp->pf_prev = psp->ps_files; psp->ps_files = pfp; psp->ps_cfile = NULL; } /* Link the services from all files together for easy referencing. */ pftopp = psp->ps_files; for (pfp = pftopp->pf_prev; pfp != NULL; pfp = pfp->pf_prev) if (pfp->pf_svc != NULL) { if (pftopp->pf_svc_last == NULL) pftopp->pf_svc = pfp->pf_svc; else pftopp->pf_svc_last->sl_next = pfp->pf_svc; pftopp->pf_svc_last = pfp->pf_svc_last; pfp->pf_svc = pfp->pf_svc_last = NULL; } /* * Count up number of services per device, including * duplicates but not including defaults. */ nsvcs = 0; for (slp = psp->ps_files->pf_svc; slp != NULL; slp = slp->sl_next) for (dlp = slp->sl_dev; dlp != NULL; dlp = dlp->dl_next) nsvcs++; /* * Get the unique devices referenced by all services. */ dnames = get_unique_devs(psp); if (dnames == NULL) { logdbg("no devices referenced by any service"); return (NULL); } ndevs = 0; for (dnp = dnames; *dnp != NULL; dnp++) ndevs++; /* * Allocate room for main structure, device records, and * per-device lists. Worst case is all devices having all * services; that's why we allocate for nsvcs * ndevs. */ osp = (struct option_state *)malloc(sizeof (*osp) + ndevs * sizeof (*dep) + nsvcs * ndevs * sizeof (*sepp)); if (osp == NULL) { logerr("unable to allocate option state structure"); free(dnames); return (NULL); } /* We're going to succeed now, so steal these over. */ osp->os_devices = dep = (struct device_entry *)(osp+1); osp->os_pfjunk = psp->ps_files; psp->ps_files = NULL; osp->os_evjunk = psp->ps_evlist; psp->ps_evlist = NULL; /* Loop over devices, install services, remove duplicates. */ sepp = (struct service_entry **)(dep + ndevs); for (dnp = dnames; *dnp != NULL; dnp++) { dep->de_name = *dnp; dep->de_services = (const struct service_entry **)sepp; sebpp = sepp; for (slp = osp->os_pfjunk->pf_svc; slp != NULL; slp = slp->sl_next) for (dlp = slp->sl_dev; dlp != NULL; dlp = dlp->dl_next) { if (dlp->dl_name == *dnp || strcmp(dlp->dl_name, *dnp) == 0) { for (se2pp = sebpp; se2pp < sepp; se2pp++) if ((*se2pp)->se_name == slp->sl_entry.se_name || strcmp((*se2pp)-> se_name, slp->sl_entry. se_name) == 0) break; /* * We retain a service if it's * unique or if its serial * number (position in the * file) is greater than than * any other. */ if (se2pp >= sepp) *sepp++ = &slp->sl_entry; else if (SESERIAL(**se2pp) < SESERIAL(slp->sl_entry)) *se2pp = &slp->sl_entry; } } /* Count up the services on this device. */ dep->de_nservices = (const struct service_entry **)sepp - dep->de_services; /* Ignore devices having no services at all. */ if (dep->de_nservices > 0) dep++; } /* Count up the devices. */ osp->os_ndevices = dep - osp->os_devices; /* Free the list of device names */ free(dnames); return (osp); } /* * Free storage unique to a given service. Pointers copied from other * services are ignored. */ static void free_service(struct service_list *slp) { struct filter_entry *fep; struct filter_entry *fen; if (!(slp->sl_entry.se_flags & SEF_CDEV)) free_device_list(slp->sl_dev); if (!(slp->sl_entry.se_flags & SEF_CFLIST)) { fep = slp->sl_entry.se_flist; while (fep != NULL) { fen = fep->fe_prevcopy ? NULL : fep->fe_prev; free(fep); fep = fen; } } if (!(slp->sl_entry.se_flags & SEF_CPPPD) && slp->sl_entry.se_pppd != NULL) free(slp->sl_entry.se_pppd); if (!(slp->sl_entry.se_flags & SEF_CSERVER) && slp->sl_entry.se_server != NULL) free(slp->sl_entry.se_server); if (!(slp->sl_entry.se_flags & SEF_CPATH) && slp->sl_entry.se_path != NULL) free(slp->sl_entry.se_path); if (!(slp->sl_entry.se_flags & SEF_CEXTRA) && slp->sl_entry.se_extra != NULL) free(slp->sl_entry.se_extra); if (!(slp->sl_entry.se_flags & SEF_CLOG) && slp->sl_entry.se_log != NULL) free(slp->sl_entry.se_log); } /* * Free a linked list of services. */ static void free_service_list(struct service_list *slp) { struct service_list *sln; while (slp != NULL) { free_service(slp); sln = slp->sl_next; free(slp); slp = sln; } } /* * Free a linked list of files and all services in those files. */ static void free_file_list(struct per_file *pfp) { struct per_file *pfn; while (pfp != NULL) { free_service(&pfp->pf_global); free_service_list(pfp->pf_svc); pfn = pfp->pf_prev; free(pfp); pfp = pfn; } } /* * Free an array of local environment variables. */ static void free_env_list(char **evlist) { char **evp; char *env; if ((evp = evlist) != NULL) { while ((env = *evp++) != NULL) free(env); free(evlist); } } /* * Add a new device (lower stream) to the list for which we're the * PPPoE server. */ static void add_new_dev(int tunfd, const char *dname) { union ppptun_name ptn; (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed", dname); if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) { logerr("PPPTUN_SCTL %s: %s", ptn.ptn_name, mystrerror(errno)); } else { logdbg("added %s", ptn.ptn_name); } } /* * Remove an existing device (lower stream) from the list for which we * were the PPPoE server. */ static void rem_old_dev(int tunfd, const char *dname) { union ppptun_name ptn; (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed", dname); if (strioctl(tunfd, PPPTUN_DCTL, &ptn, sizeof (ptn), 0) < 0) { logerr("PPPTUN_DCTL %s: %s", ptn.ptn_name, mystrerror(errno)); } else { logdbg("removed %s", ptn.ptn_name); } } /* * Get a list of all of the devices currently plumbed for PPPoE. This * is used for supporting the "*" and "all" device aliases. */ static void get_device_list(struct parse_state *psp, int tunfd) { struct device_list *dlp; struct device_list **dlpp; struct device_list *dlalt; struct device_list **dl2pp; struct device_list *dla; int i; union ppptun_name ptn; char *cp; /* First pass; just allocate space for all *:pppoe* devices */ dlpp = &psp->ps_star; dl2pp = &dlalt; for (i = 0; ; i++) { ptn.ptn_index = i; if (strioctl(tunfd, PPPTUN_GNNAME, &ptn, sizeof (ptn), sizeof (ptn)) < 0) { logerr("PPPTUN_GNNAME %d: %s", i, mystrerror(errno)); break; } if (ptn.ptn_name[0] == '\0') break; if ((cp = strchr(ptn.ptn_name, ':')) == NULL || strncmp(cp, ":pppoe", 6) != 0 || (cp[6] != '\0' && strcmp(cp+6, "d") != 0)) continue; *cp = '\0'; dlp = (struct device_list *)malloc(sizeof (*dlp) + strlen(ptn.ptn_name) + 1); if (dlp == NULL) break; dlp->dl_name = (const char *)(dlp + 1); (void) strcpy((char *)(dlp + 1), ptn.ptn_name); if (cp[6] == '\0') { *dlpp = dlp; dlpp = &dlp->dl_next; } else { *dl2pp = dlp; dl2pp = &dlp->dl_next; } } *dlpp = NULL; *dl2pp = NULL; /* Second pass; eliminate improperly plumbed devices */ for (dlpp = &psp->ps_star; (dlp = *dlpp) != NULL; ) { for (dla = dlalt; dla != NULL; dla = dla->dl_next) if (strcmp(dla->dl_name, dlp->dl_name) == 0) break; if (dla == NULL) { *dlpp = dlp->dl_next; free(dlp); } else { dlpp = &dlp->dl_next; } } free_device_list(dlalt); /* Add in "*" so we can always handle dynamic plumbing. */ dlp = (struct device_list *)malloc(sizeof (*dlp) + 2); if (dlp != NULL) { dlp->dl_name = (const char *)(dlp + 1); (void) strcpy((char *)(dlp + 1), "*"); dlp->dl_next = psp->ps_star; psp->ps_star = dlp; } } /* * Set logging subsystem back to configured global default values. */ void global_logging(void) { log_for_service(glob_svc.se_log, glob_svc.se_debug); } /* * Handle SIGHUP -- reparse command line and all configuration files. * When reparsing is complete, free old parsed data and replace with * new. */ void parse_options(int tunfd, int argc, char **argv) { struct parse_state pstate; struct per_file *argpf; struct option_state *newopt; const char **dnames; const char **dnp; const struct device_entry *newdep, *newmax; const struct device_entry *olddep, *oldmax; int cmpval; struct service_entry newglobsvc, *mainsvc; /* Note that all per_file structures must be freeable */ argpf = (struct per_file *)calloc(sizeof (*argpf), 1); if (argpf == NULL) { return; } (void) memset(&pstate, '\0', sizeof (pstate)); pstate.ps_state = ksDefault; pstate.ps_cfile = argpf; pstate.ps_csvc = &argpf->pf_global; argpf->pf_global.sl_parse = &pstate; argpf->pf_name = "command line"; /* Default is 1 -- errors only */ argpf->pf_global.sl_entry.se_debug++; argpf->pf_global.sl_entry.se_name = ""; /* Get list of all devices */ get_device_list(&pstate, tunfd); /* Parse options from command line and main configuration file. */ pstate.ps_flags |= PSF_SETLEVEL; parse_arg_list(&pstate, argc, argv); parse_file(&pstate, "/etc/ppp/pppoe"); pstate.ps_flags &= ~PSF_SETLEVEL; /* * At this point, global options from the main configuration * file are pointed to by ps_files, and options from command * line are in argpf. We need to pull three special options * from these -- wildcard, debug, and log. Note that the main * options file overrides the command line. This is * intentional. The semantics are such that the system * behaves as though the main configuration file were * "included" from the command line, and thus options there * override the command line. This may seem odd, but at least * it's self-consistent. */ newglobsvc = argpf->pf_global.sl_entry; if (pstate.ps_files != NULL) { mainsvc = &pstate.ps_files->pf_global.sl_entry; if (mainsvc->se_log != NULL) newglobsvc.se_log = mainsvc->se_log; if (mainsvc->se_flags & (SEF_WILD|SEF_NOWILD)) newglobsvc.se_flags = (newglobsvc.se_flags & ~(SEF_WILD|SEF_NOWILD)) | (mainsvc->se_flags & (SEF_WILD|SEF_NOWILD)); if (mainsvc->se_flags & SEF_DEBUGCLR) newglobsvc.se_debug = 0; newglobsvc.se_debug += mainsvc->se_debug; } glob_svc = newglobsvc; global_logging(); /* Get the list of devices referenced by configuration above. */ dnames = get_unique_devs(&pstate); if (dnames != NULL) { /* Read per-device configuration files. */ pstate.ps_flags |= PSF_PERDEV; for (dnp = dnames; *dnp != NULL; dnp++) parse_file(&pstate, *dnp); pstate.ps_flags &= ~PSF_PERDEV; free(dnames); } file_end(&pstate); /* * Convert parsed data structures into per-device structures. * (Invert the table.) */ newopt = organize_state(&pstate); /* If we're going to free the file name, then stop logging there. */ if (newopt == NULL && glob_svc.se_log != NULL) { glob_svc.se_log = NULL; global_logging(); } /* * Unless an error has occurred, these pointers are normally * all NULL. Nothing is freed until the file is re-read. */ free_file_list(pstate.ps_files); free_file_list(pstate.ps_cfile); free_device_list(pstate.ps_star); free_env_list(pstate.ps_evlist); /* * Match up entries on device list. Detach devices no longer * referenced. Attach ones now referenced. (The use of null * pointers here may look fishy, but it actually works. * NULL>=NULL is always true.) */ if (newopt != NULL) { newdep = newopt->os_devices; newmax = newdep + newopt->os_ndevices; } else { newdep = newmax = NULL; } if (cur_options != NULL) { olddep = cur_options->os_devices; oldmax = olddep + cur_options->os_ndevices; } else { olddep = oldmax = NULL; } while ((newdep != NULL && newdep < newmax) || (olddep != NULL && olddep < oldmax)) { if (newdep < newmax) { if (olddep >= oldmax) { add_new_dev(tunfd, newdep->de_name); newdep++; } else { cmpval = strcmp(newdep->de_name, olddep->de_name); if (cmpval < 0) { /* Brand new device seen. */ add_new_dev(tunfd, newdep->de_name); newdep++; } else if (cmpval == 0) { /* Existing device; skip it. */ newdep++; olddep++; } /* No else clause -- removal is below */ } } if (olddep < oldmax) { if (newdep >= newmax) { rem_old_dev(tunfd, olddep->de_name); olddep++; } else { cmpval = strcmp(newdep->de_name, olddep->de_name); if (cmpval > 0) { /* Old device is gone */ rem_old_dev(tunfd, olddep->de_name); olddep++; } else if (cmpval == 0) { /* Existing device; skip it. */ newdep++; olddep++; } /* No else clause -- insert handled above */ } } } /* Discard existing parsed data storage. */ if (cur_options != NULL) { free_file_list(cur_options->os_pfjunk); free_env_list(cur_options->os_evjunk); free(cur_options); } /* Install new. */ cur_options = newopt; } /* * Check if configured filters permit requesting client to use a given * service. Note -- filters are stored in reverse order in order to * make file-inclusion work as expected. Thus, the "first match" * filter rule becomes "last match" here. */ static boolean_t allow_service(const struct service_entry *sep, const ppptun_atype *pap) { const struct filter_entry *fep; const struct filter_entry *lmatch; boolean_t anynonexcept = B_FALSE; const uchar_t *upt; const uchar_t *macp; const uchar_t *maskp; int i; lmatch = NULL; for (fep = sep->se_flist; fep != NULL; fep = fep->fe_prev) { anynonexcept |= !fep->fe_isexcept; upt = pap->pta_pppoe.ptma_mac; macp = fep->fe_mac.ether_addr_octet; maskp = fep->fe_mask.ether_addr_octet; for (i = sizeof (pap->pta_pppoe.ptma_mac); i > 0; i--) if (((*macp++ ^ *upt++) & *maskp++) != 0) break; if (i <= 0) lmatch = fep; } if (lmatch == NULL) { /* * Assume reject by default if any positive-match * (non-except) filters are given. Otherwise, if * there are no positive-match filters, then * non-matching means accept by default. */ return (!anynonexcept); } return (!lmatch->fe_isexcept); } /* * Locate available service(s) based on client request. Assumes that * outp points to a buffer of at least size PPPOE_MSGMAX. Creates a * PPPoE response message in outp. Returns count of matched services * and (through *srvp) a pointer to the last (or only) service. If * some error is found in the request, an error string is added and -1 * is returned; the caller should just send the message without * alteration. */ int locate_service(poep_t *poep, int plen, const char *iname, ppptun_atype *pap, uint32_t *outp, void **srvp) { poep_t *opoe; const uint8_t *tagp; const char *cp; int ttyp; int tlen; int nsvcs; const struct device_entry *dep, *depe; const struct device_entry *wdep; const struct service_entry **sepp, **seppe; const struct service_entry *sep; char *str; boolean_t ispadi; ispadi = poep->poep_code == POECODE_PADI; opoe = poe_mkheader(outp, ispadi ? POECODE_PADO : POECODE_PADS, 0); *srvp = NULL; if (cur_options == NULL) return (0); /* Search for named device (lower stream) in tables. */ dep = cur_options->os_devices; depe = dep + cur_options->os_ndevices; wdep = NULL; if ((cp = strchr(iname, ':')) != NULL) tlen = cp - iname; else tlen = strlen(iname); for (; dep < depe; dep++) if (strncmp(iname, dep->de_name, tlen) == 0 && dep->de_name[tlen] == '\0') break; else if (dep->de_name[0] == '*' && dep->de_name[1] == '\0') wdep = dep; if (dep >= depe) dep = wdep; /* * Return if interface not found. Zero-service case can't * occur, since devices with no services aren't included in * the list, but the code is just being safe here. */ if (dep == NULL || dep->de_services == NULL || dep->de_nservices <= 0) return (0); /* * Loop over tags in client message and process them. * Services must be matched against our list. Host-Uniq and * Relay-Session-Id must be copied to the reply. All others * must be discarded. */ nsvcs = 0; sepp = dep->de_services; tagp = (const uint8_t *)(poep + 1); while (poe_tagcheck(poep, plen, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; tlen = POET_GET_LENG(tagp); switch (ttyp) { case POETT_SERVICE: /* Service-Name */ /* * Allow only one. (Note that this test works * because there's always at least one service * per device; otherwise, the device is * removed from the list.) */ if (sepp != dep->de_services) { if (nsvcs != -1) (void) poe_add_str(opoe, POETT_NAMERR, "Too many Service-Name tags"); nsvcs = -1; break; } seppe = sepp + dep->de_nservices; if (tlen == 0) { /* * If config specifies "nowild" in a * global context, then we don't * respond to wildcard PADRs. The * client must know the exact service * name to get access. */ if (!ispadi && (glob_svc.se_flags & SEF_NOWILD)) sepp = seppe; while (sepp < seppe) { sep = *sepp++; if (sep->se_name[0] == '\0' || (sep->se_flags & SEF_NOWILD) || !allow_service(sep, pap)) continue; *srvp = (void *)sep; /* * RFC requires that PADO includes the * wildcard service request in response * to PADI. */ if (ispadi && nsvcs == 0 && !(glob_svc.se_flags & SEF_NOWILD)) (void) poe_tag_copy(opoe, tagp); nsvcs++; (void) poe_add_str(opoe, POETT_SERVICE, sep->se_name); /* If PADR, then one is enough */ if (!ispadi) break; } /* Just for generating error messages */ if (nsvcs == 0) (void) poe_tag_copy(opoe, tagp); } else { /* * Clients's requested service must appear in * reply. */ (void) poe_tag_copy(opoe, tagp); /* Requested specific service; find it. */ cp = (char *)POET_DATA(tagp); while (sepp < seppe) { sep = *sepp++; if (strlen(sep->se_name) == tlen && strncasecmp(sep->se_name, cp, tlen) == 0) { if (allow_service(sep, pap)) { nsvcs++; *srvp = (void *)sep; } break; } } } /* * Allow service definition to override * AC-Name (concentrator [server] name) field. */ if (*srvp != NULL) { sep = (const struct service_entry *)*srvp; log_for_service(sep->se_log, sep->se_debug); str = "Solaris PPPoE"; if (sep->se_server != NULL) str = sep->se_server; (void) poe_add_str(opoe, POETT_ACCESS, str); } break; /* Ones we should discard */ case POETT_ACCESS: /* AC-Name */ case POETT_COOKIE: /* AC-Cookie */ case POETT_NAMERR: /* Service-Name-Error */ case POETT_SYSERR: /* AC-System-Error */ case POETT_GENERR: /* Generic-Error */ case POETT_HURL: /* Host-URL */ case POETT_MOTM: /* Message-Of-The-Minute */ case POETT_RTEADD: /* IP-Route-Add */ case POETT_VENDOR: /* Vendor-Specific */ case POETT_MULTI: /* Multicast-Capable */ default: break; /* Ones we should copy */ case POETT_UNIQ: /* Host-Uniq */ case POETT_RELAY: /* Relay-Session-Id */ (void) poe_tag_copy(opoe, tagp); break; } tagp = POET_NEXT(tagp); } return (nsvcs); } /* * Like fgetc, but reads from a string. */ static int sgetc(void *arg) { char **cpp = (char **)arg; if (**cpp == '\0') return (EOF); return (*(*cpp)++); } /* * Given a service structure, launch pppd. Called by handle_input() * in pppoed.c if locate_service() [above] finds exactly one service * matching a PADR. */ int launch_service(int tunfd, poep_t *poep, void *srvp, struct ppptun_control *ptc) { const struct service_entry *sep = (const struct service_entry *)srvp; const char *path; const char *extra; const char *pppd; const char *cp; pid_t pidv; int newtun; struct ppptun_peer ptp; union ppptun_name ptn; const char *args[MAXARGS]; struct strbuf ctrl; struct strbuf data; const char **cpp; char *sptr; char *spv; int slen; int retv; char keybuf[MAX_KEYWORD]; assert(sep != NULL); /* Get tunnel driver connection for new PPP session. */ newtun = open(tunnam, O_RDWR); if (newtun == -1) goto syserr; /* Set this session up for standard PPP and client's address. */ (void) memset(&ptp, '\0', sizeof (ptp)); ptp.ptp_style = PTS_PPPOE; ptp.ptp_address = ptc->ptc_address; if (strioctl(newtun, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) goto syserr; ptp.ptp_rsessid = ptp.ptp_lsessid; if (strioctl(newtun, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) goto syserr; /* Attach the requested lower stream. */ cp = strchr(ptc->ptc_name, ':'); if (cp == NULL) cp = ptc->ptc_name + strlen(ptc->ptc_name); (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoe", cp-ptc->ptc_name, ptc->ptc_name); if (strioctl(newtun, PPPTUN_SDATA, &ptn, sizeof (ptn), 0) < 0) goto syserr; (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoed", cp-ptc->ptc_name, ptc->ptc_name); if (strioctl(newtun, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) goto syserr; pidv = fork(); if (pidv == (pid_t)-1) goto syserr; if (pidv == (pid_t)0) { /* * Use syslog only in order to avoid mixing log messages * in regular files. */ close_log_files(); if ((path = sep->se_path) == NULL) path = "/usr/bin/pppd"; if ((extra = sep->se_extra) == NULL) extra = "plugin pppoe.so directtty"; if ((pppd = sep->se_pppd) == NULL) pppd = ""; /* Concatenate these. */ slen = strlen(path) + strlen(extra) + strlen(pppd) + 3; if ((sptr = (char *)malloc(slen)) == NULL) goto bail_out; (void) strcpy(sptr, path); (void) strcat(sptr, " "); (void) strcat(sptr, extra); (void) strcat(sptr, " "); (void) strcat(sptr, pppd); /* Parse out into arguments */ cpp = args; spv = sptr; while (cpp < args + MAXARGS - 1) { retv = getkeyword(NULL, keybuf, sizeof (keybuf), sgetc, (void *)&spv, 1); if (retv != 1) *cpp++ = strsave(keybuf); if (retv != 0) break; } *cpp = NULL; if (cpp == args) goto bail_out; /* * Fix tunnel device on stdin/stdout and error file on * stderr. */ if (newtun != 0 && dup2(newtun, 0) < 0) goto bail_out; if (newtun != 1 && dup2(newtun, 1) < 0) goto bail_out; if (newtun > 1) (void) close(newtun); if (tunfd > 1) (void) close(tunfd); (void) close(2); (void) open("/etc/ppp/pppoe-errors", O_WRONLY | O_APPEND | O_CREAT, 0600); /* * Change GID first, for obvious reasons. Note that * we log any problems to syslog, not the errors file. * The errors file is intended for problems in the * exec'd program. */ if ((sep->se_flags & SEF_GIDSET) && setgid(sep->se_gid) == -1) { cp = mystrerror(errno); reopen_log(); logerr("setgid(%d): %s", sep->se_gid, cp); goto logged; } if ((sep->se_flags & SEF_UIDSET) && setuid(sep->se_uid) == -1) { cp = mystrerror(errno); reopen_log(); logerr("setuid(%d): %s", sep->se_uid, cp); goto logged; } /* Run pppd */ path = args[0]; cp = strrchr(args[0], '/'); if (cp != NULL && cp[1] != '\0') args[0] = cp+1; errno = 0; (void) execv(path, (char * const *)args); newtun = 0; /* * Exec failure; attempt to log the problem and send a * PADT to the client so that it knows the session * went south. */ bail_out: cp = mystrerror(errno); reopen_log(); logerr("\"%s\": %s", (sptr == NULL ? path : sptr), cp); logged: poep = poe_mkheader(pkt_output, POECODE_PADT, ptp.ptp_lsessid); poep->poep_session_id = htons(ptp.ptp_lsessid); (void) poe_add_str(poep, POETT_SYSERR, cp); (void) sleep(1); ctrl.len = sizeof (*ptc); ctrl.buf = (caddr_t)ptc; data.len = poe_length(poep) + sizeof (*poep); data.buf = (caddr_t)poep; if (putmsg(newtun, &ctrl, &data, 0) < 0) { logerr("putmsg %s: %s", ptc->ptc_name, mystrerror(errno)); } exit(1); } (void) close(newtun); /* Give session ID to client in reply. */ poep->poep_session_id = htons(ptp.ptp_lsessid); return (1); syserr: /* Peer doesn't know session ID yet; hope for the best. */ retv = errno; if (newtun >= 0) (void) close(newtun); (void) poe_add_str(poep, POETT_SYSERR, mystrerror(retv)); return (0); } /* * This is pretty awful -- it uses recursion to print a simple list. * It's just for debug, though, and does a reasonable job of printing * the filters in the right order. */ static void print_filter_list(FILE *fp, struct filter_entry *fep) { if (fep->fe_prev != NULL) print_filter_list(fp, fep->fe_prev); (void) fprintf(fp, "\t\t MAC %s", ehost2(&fep->fe_mac)); (void) fprintf(fp, ", mask %s%s\n", ehost2(&fep->fe_mask), (fep->fe_isexcept ? ", except" : "")); } /* * Write summary of parsed configuration data to given file. */ void dump_configuration(FILE *fp) { const struct device_entry *dep; const struct service_entry *sep, **sepp; struct per_file *pfp; int i, j; (void) fprintf(fp, "Will%s respond to wildcard queries.\n", (glob_svc.se_flags & SEF_NOWILD) ? " not" : ""); (void) fprintf(fp, "Global debug level %d, log to %s; current level %d\n", glob_svc.se_debug, ((glob_svc.se_log == NULL || *glob_svc.se_log == '\0') ? "syslog" : glob_svc.se_log), log_level); if (cur_options == NULL) { (void) fprintf(fp, "No current configuration.\n"); return; } (void) fprintf(fp, "Current configuration:\n"); (void) fprintf(fp, " %d device(s):\n", cur_options->os_ndevices); dep = cur_options->os_devices; for (i = 0; i < cur_options->os_ndevices; i++, dep++) { (void) fprintf(fp, "\t%s: %d service(s):\n", dep->de_name, dep->de_nservices); sepp = dep->de_services; for (j = 0; j < dep->de_nservices; j++, sepp++) { sep = *sepp; (void) fprintf(fp, "\t %s: debug level %d", sep->se_name, sep->se_debug); if (sep->se_flags & SEF_UIDSET) (void) fprintf(fp, ", UID %u", sep->se_uid); if (sep->se_flags & SEF_GIDSET) (void) fprintf(fp, ", GID %u", sep->se_gid); if (sep->se_flags & SEF_WILD) (void) fprintf(fp, ", wildcard"); else if (sep->se_flags & SEF_NOWILD) (void) fprintf(fp, ", nowildcard"); else (void) fprintf(fp, ", wildcard (default)"); (void) putc('\n', fp); if (sep->se_server != NULL) (void) fprintf(fp, "\t\tserver \"%s\"\n", sep->se_server); if (sep->se_pppd != NULL) (void) fprintf(fp, "\t\tpppd \"%s\"\n", sep->se_pppd); if (sep->se_path != NULL) (void) fprintf(fp, "\t\tpath \"%s\"\n", sep->se_path); if (sep->se_extra != NULL) (void) fprintf(fp, "\t\textra \"%s\"\n", sep->se_extra); if (sep->se_log != NULL) (void) fprintf(fp, "\t\tlog \"%s\"\n", sep->se_log); if (sep->se_flist != NULL) { (void) fprintf(fp, "\t\tfilter list:\n"); print_filter_list(fp, sep->se_flist); } } } (void) fprintf(fp, "\nConfiguration read from:\n"); for (pfp = cur_options->os_pfjunk; pfp != NULL; pfp = pfp->pf_prev) { (void) fprintf(fp, " %s: %d service(s)\n", pfp->pf_name, pfp->pf_nsvc); } } /* * 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 */ /* * PPPoE Client-mode "chat" utility for use with Solaris PPP 4.0. * * 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 "common.h" #include "logging.h" /* * This value, currently set to the characters "POE1," is used to * distinguish among control messages from multiple lower streams * under /dev/sppp. This feature is needed to support PPP translation * (LAC-like behavior), but isn't currently used. */ #define PPPOE_DISCRIM 0x504F4531 /* milliseconds between retries */ #define PADI_RESTART_TIME 500 #define PADR_RESTART_TIME 2000 /* default inquiry mode timer in milliseconds. */ #define PADI_INQUIRY_DWELL 3000 /* maximum timer value in milliseconds */ #define RESTART_LIMIT 5000 char *myname; /* copy of argv[0] for error messages */ static int verbose; /* -v flag given */ static int onlyflag; /* keyword "only" at end of command line */ static char *service = ""; /* saved service name from command line */ static int pado_wait_time = 0; /* see main() */ static int pads_wait_time = PADR_RESTART_TIME; static int tunfd; /* open connection to sppptun driver */ static struct timeval tvstart; /* time of last PADI/PADR transmission */ struct server_filter { struct server_filter *sf_next; /* Next filter in list */ struct ether_addr sf_mac; /* Ethernet address */ struct ether_addr sf_mask; /* Mask (0 or 0xFF in each byte) */ const char *sf_name; /* String for AC-Name compare */ boolean_t sf_hasmac; /* Set if string could be MAC */ boolean_t sf_isexcept; /* Ignore server if matching */ }; /* List of filters defined on command line. */ static struct server_filter *sfhead, *sftail; /* * PPPoE Client State Machine */ /* Client events */ #define PCSME_CLOSE 0 /* User close */ #define PCSME_OPEN 1 /* User open */ #define PCSME_TOP 2 /* Timeout+ (counter non-zero) */ #define PCSME_TOM 3 /* Timeout- (counter zero) */ #define PCSME_RPADT 4 /* Receive PADT (unexpected here) */ #define PCSME_RPADOP 5 /* Receive desired PADO */ #define PCSME_RPADO 6 /* Receive ordinary PADO */ #define PCSME_RPADS 7 /* Receive PADS */ #define PCSME_RPADSN 8 /* Receive bad (errored) PADS */ #define PCSME__MAX 9 /* Client states */ #define PCSMS_DEAD 0 /* Initial state */ #define PCSMS_INITSENT 1 /* PADI sent */ #define PCSMS_OFFRRCVD 2 /* PADO received */ #define PCSMS_REQSENT 3 /* PADR sent */ #define PCSMS_CONVERS 4 /* Conversational */ #define PCSMS__MAX 5 /* Client actions */ #define PCSMA_NONE 0 /* Do nothing */ #define PCSMA_FAIL 1 /* Unrecoverable error */ #define PCSMA_SPADI 2 /* Send PADI */ #define PCSMA_ADD 3 /* Add ordinary server to list */ #define PCSMA_SPADR 4 /* Send PADR to top server */ #define PCSMA_SPADRP 5 /* Send PADR to this server (make top) */ #define PCSMA_SPADRN 6 /* Send PADR to next (or terminate) */ #define PCSMA_OPEN 7 /* Start PPP */ #define PCSMA__MAX 8 static uint8_t client_next_state[PCSMS__MAX][PCSME__MAX] = { /* 0 PCSMS_DEAD Initial state */ { PCSMS_DEAD, /* PCSME_CLOSE User close */ PCSMS_INITSENT, /* PCSME_OPEN User open */ PCSMS_DEAD, /* PCSME_TOP Timeout+ */ PCSMS_DEAD, /* PCSME_TOM Timeout- */ PCSMS_DEAD, /* PCSME_RPADT Receive PADT */ PCSMS_DEAD, /* PCSME_RPADOP Receive desired PADO */ PCSMS_DEAD, /* PCSME_RPADO Receive ordinary PADO */ PCSMS_DEAD, /* PCSME_RPADS Receive PADS */ PCSMS_DEAD, /* PCSME_RPADSN Receive bad PADS */ }, /* 1 PCSMS_INITSENT PADI sent */ { PCSMS_DEAD, /* PCSME_CLOSE User close */ PCSMS_INITSENT, /* PCSME_OPEN User open */ PCSMS_INITSENT, /* PCSME_TOP Timeout+ */ PCSMS_DEAD, /* PCSME_TOM Timeout- */ PCSMS_DEAD, /* PCSME_RPADT Receive PADT */ PCSMS_REQSENT, /* PCSME_RPADOP Receive desired PADO */ PCSMS_OFFRRCVD, /* PCSME_RPADO Receive ordinary PADO */ PCSMS_INITSENT, /* PCSME_RPADS Receive PADS */ PCSMS_INITSENT, /* PCSME_RPADSN Receive bad PADS */ }, /* 2 PCSMS_OFFRRCVD PADO received */ { PCSMS_DEAD, /* PCSME_CLOSE User close */ PCSMS_INITSENT, /* PCSME_OPEN User open */ PCSMS_REQSENT, /* PCSME_TOP Timeout+ */ PCSMS_REQSENT, /* PCSME_TOM Timeout- */ PCSMS_DEAD, /* PCSME_RPADT Receive PADT */ PCSMS_REQSENT, /* PCSME_RPADOP Receive desired PADO */ PCSMS_OFFRRCVD, /* PCSME_RPADO Receive ordinary PADO */ PCSMS_OFFRRCVD, /* PCSME_RPADS Receive PADS */ PCSMS_OFFRRCVD, /* PCSME_RPADSN Receive bad PADS */ }, /* 3 PCSMS_REQSENT PADR sent */ { PCSMS_DEAD, /* PCSME_CLOSE User close */ PCSMS_INITSENT, /* PCSME_OPEN User open */ PCSMS_REQSENT, /* PCSME_TOP Timeout+ */ PCSMS_REQSENT, /* PCSME_TOM Timeout- */ PCSMS_DEAD, /* PCSME_RPADT Receive PADT */ PCSMS_REQSENT, /* PCSME_RPADOP Receive desired PADO */ PCSMS_REQSENT, /* PCSME_RPADO Receive ordinary PADO */ PCSMS_CONVERS, /* PCSME_RPADS Receive PADS */ PCSMS_REQSENT, /* PCSME_RPADSN Receive bad PADS */ }, /* 4 PCSMS_CONVERS Conversational */ { PCSMS_DEAD, /* PCSME_CLOSE User close */ PCSMS_INITSENT, /* PCSME_OPEN User open */ PCSMS_CONVERS, /* PCSME_TOP Timeout+ */ PCSMS_CONVERS, /* PCSME_TOM Timeout- */ PCSMS_DEAD, /* PCSME_RPADT Receive PADT */ PCSMS_CONVERS, /* PCSME_RPADOP Receive desired PADO */ PCSMS_CONVERS, /* PCSME_RPADO Receive ordinary PADO */ PCSMS_CONVERS, /* PCSME_RPADS Receive PADS */ PCSMS_CONVERS, /* PCSME_RPADSN Receive bad PADS */ }, }; static uint8_t client_action[PCSMS__MAX][PCSME__MAX] = { /* 0 PCSMS_DEAD Initial state */ { PCSMA_NONE, /* PCSME_CLOSE User close */ PCSMA_SPADI, /* PCSME_OPEN User open */ PCSMA_NONE, /* PCSME_TOP Timeout+ */ PCSMA_NONE, /* PCSME_TOM Timeout- */ PCSMA_NONE, /* PCSME_RPADT Receive PADT */ PCSMA_NONE, /* PCSME_RPADOP Receive desired PADO */ PCSMA_NONE, /* PCSME_RPADO Receive ordinary PADO */ PCSMA_NONE, /* PCSME_RPADS Receive PADS */ PCSMA_NONE, /* PCSME_RPADSN Receive bad PADS */ }, /* 1 PCSMS_INITSENT PADI sent */ { PCSMA_FAIL, /* PCSME_CLOSE User close */ PCSMA_SPADI, /* PCSME_OPEN User open */ PCSMA_SPADI, /* PCSME_TOP Timeout+ */ PCSMA_FAIL, /* PCSME_TOM Timeout- */ PCSMA_FAIL, /* PCSME_RPADT Receive PADT */ PCSMA_SPADRP, /* PCSME_RPADOP Receive desired PADO */ PCSMA_ADD, /* PCSME_RPADO Receive ordinary PADO */ PCSMA_NONE, /* PCSME_RPADS Receive PADS */ PCSMA_NONE, /* PCSME_RPADSN Receive bad PADS */ }, /* 2 PCSMS_OFFRRCVD PADO received */ { PCSMA_FAIL, /* PCSME_CLOSE User close */ PCSMA_SPADI, /* PCSME_OPEN User open */ PCSMA_SPADR, /* PCSME_TOP Timeout+ */ PCSMA_SPADR, /* PCSME_TOM Timeout- */ PCSMA_FAIL, /* PCSME_RPADT Receive PADT */ PCSMA_SPADRP, /* PCSME_RPADOP Receive desired PADO */ PCSMA_ADD, /* PCSME_RPADO Receive ordinary PADO */ PCSMA_NONE, /* PCSME_RPADS Receive PADS */ PCSMA_NONE, /* PCSME_RPADSN Receive bad PADS */ }, /* 3 PCSMS_REQSENT PADR sent */ { PCSMA_FAIL, /* PCSME_CLOSE User close */ PCSMA_SPADI, /* PCSME_OPEN User open */ PCSMA_SPADR, /* PCSME_TOP Timeout+ */ PCSMA_SPADRN, /* PCSME_TOM Timeout- */ PCSMA_FAIL, /* PCSME_RPADT Receive PADT */ PCSMA_ADD, /* PCSME_RPADOP Receive desired PADO */ PCSMA_ADD, /* PCSME_RPADO Receive ordinary PADO */ PCSMA_OPEN, /* PCSME_RPADS Receive PADS */ PCSMA_SPADRN, /* PCSME_RPADSN Receive bad PADS */ }, /* 4 PCSMS_CONVERS Conversational */ { PCSMA_FAIL, /* PCSME_CLOSE User close */ PCSMA_SPADI, /* PCSME_OPEN User open */ PCSMA_FAIL, /* PCSME_TOP Timeout+ */ PCSMA_FAIL, /* PCSME_TOM Timeout- */ PCSMA_FAIL, /* PCSME_RPADT Receive PADT */ PCSMA_NONE, /* PCSME_RPADOP Receive desired PADO */ PCSMA_NONE, /* PCSME_RPADO Receive ordinary PADO */ PCSMA_NONE, /* PCSME_RPADS Receive PADS */ PCSMA_NONE, /* PCSME_RPADSN Receive bad PADS */ }, }; /* * PPPoE Message structure -- holds data from a received PPPoE * message. These are copied and saved when queuing offers from * possible servers. */ typedef struct poesm_s { struct poesm_s *poemsg_next; /* Next message in list */ const poep_t *poemsg_data; /* Pointer to PPPoE packet */ int poemsg_len; /* Length of packet */ ppptun_atype poemsg_sender; /* Address of sender */ const char *poemsg_iname; /* Name of input interface */ } poemsg_t; /* * PPPoE State Machine structure -- holds state of PPPoE negotiation; * currently, there's exactly one of these per pppoec instance. */ typedef struct { int poesm_state; /* PCSMS_* */ int poesm_timer; /* Milliseconds to next TO */ int poesm_count; /* Retry countdown */ int poesm_interval; /* Reload value */ uint32_t poesm_sequence; /* Sequence for PADR */ poemsg_t *poesm_firstoff; /* Queue of valid offers; */ poemsg_t *poesm_lastoff; /* first is best offer */ poemsg_t *poesm_tried; /* Tried and failed offers */ int poesm_localid; /* Local session ID (driver) */ } poesm_t; /* * Convert an internal PPPoE event code number into a printable * string. */ static const char * poe_event(int event) { static const char *poeevent[PCSME__MAX] = { "Close", "Open", "TO+", "TO-", "rPADT", "rPADO+", "rPADO", "rPADS", "rPADS-" }; if (event < 0 || event >= PCSME__MAX) { return ("?"); } return (poeevent[event]); } /* * Convert an internal PPPoE state number into a printable string. */ static const char * poe_state(int state) { static const char *poestate[PCSMS__MAX] = { "Dead", "InitSent", "OffrRcvd", "ReqSent", "Convers", }; if (state < 0 || state >= PCSMS__MAX) { return ("?"); } return (poestate[state]); } /* * Convert an internal PPPoE action number into a printable string. */ static const char * poe_action(int act) { static const char *poeaction[PCSMA__MAX] = { "None", "Fail", "SendPADI", "Add", "SendPADR", "SendPADR+", "SendPADR-", "Open" }; if (act < 0 || act >= PCSMA__MAX) { return ("?"); } return (poeaction[act]); } /* * This calls mygetmsg (which discards partial messages as needed) and * logs errors as appropriate. */ static int pppoec_getmsg(int fd, struct strbuf *ctrl, struct strbuf *data, int *flags) { int retv; for (;;) { retv = mygetmsg(fd, ctrl, data, flags); if (retv == 0) break; if (retv < 0) { if (errno == EINTR) continue; logstrerror("getmsg"); break; } if (verbose) { if (!(retv & (MORECTL | MOREDATA))) logerr("%s: discard: " "unexpected status %d\n", myname, retv); else logerr("%s: discard: " "truncated %s%smessage\n", myname, retv & MORECTL ? "control " : "", retv & MOREDATA ? "data " : ""); } } return (retv); } /* * Connect the control path to the lower stream of interest. This * must be called after opening the tunnel driver in order to * establish the interface to be used for signaling. Returns local * session ID number. */ static int set_control(const char *dname) { struct ppptun_peer ptp; union ppptun_name ptn; /* Fetch the local session ID first. */ (void) memset(&ptp, '\0', sizeof (ptp)); ptp.ptp_style = PTS_PPPOE; if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) { logstrerror("PPPTUN_SPEER"); exit(1); } /* Connect to lower stream. */ (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%s:pppoed", dname); if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) { logerr("%s: PPPTUN_SCTL %s: %s\n", myname, ptn.ptn_name, mystrerror(errno)); exit(1); } return (ptp.ptp_lsessid); } /* * Check if standard input is actually a viable connection to the * tunnel driver. This is the normal mode of operation with pppd; the * tunnel driver is opened by pppd as the tty and pppoec is exec'd as * the connect script. */ static void check_stdin(void) { struct ppptun_info pti; union ppptun_name ptn; if (strioctl(0, PPPTUN_GDATA, &ptn, 0, sizeof (ptn)) < 0) { if (errno == EINVAL) logerr("%s: PPPoE operation requires " "the use of a tunneling device\n", myname); else logstrerror("PPPTUN_GDATA"); exit(1); } if (ptn.ptn_name[0] != '\0') { if (strioctl(0, PPPTUN_GINFO, &pti, 0, sizeof (pti)) < 0) { logstrerror("PPPTUN_GINFO"); exit(1); } if (pti.pti_style != PTS_PPPOE) { logerr("%s: Cannot connect to server " "using PPPoE; stream already set to style %d\n", myname, pti.pti_style); exit(1); } if (verbose) logerr("%s: Warning: PPPoE data link " "already connected\n", myname); exit(0); } /* Standard input is the tunnel driver; use it. */ tunfd = 0; } /* * Write a summary of a PPPoE message to the given file. This is used * for logging and to display received offers in the inquiry (-i) mode. */ static void display_pppoe(FILE *out, const poep_t *poep, int plen, const ppptun_atype *pap) { int ttyp; int tlen; const uint8_t *tagp; const uint8_t *dp; const char *str; poer_t poer; uint32_t mask; if (out == stderr) logerr(" "); /* Give us a timestamp */ /* Print name of sender. */ (void) fprintf(out, "%-16s ", ehost(pap)); /* Loop through tags and print each. */ tagp = (const uint8_t *)(poep + 1); while (poe_tagcheck(poep, plen, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; tlen = POET_GET_LENG(tagp); dp = POET_DATA(tagp); str = NULL; switch (ttyp) { case POETT_SERVICE: /* Service-Name */ str = "Svc"; break; case POETT_ACCESS: /* AC-Name */ str = "Name"; break; case POETT_UNIQ: /* Host-Uniq */ str = "Uniq"; break; case POETT_COOKIE: /* AC-Cookie */ str = "Cookie"; break; case POETT_VENDOR: /* Vendor-Specific */ break; case POETT_RELAY: /* Relay-Session-Id */ str = "Relay"; break; case POETT_NAMERR: /* Service-Name-Error */ str = "SvcNameErr"; break; case POETT_SYSERR: /* AC-System-Error */ str = "SysErr"; break; case POETT_GENERR: /* Generic-Error */ str = "GenErr"; break; case POETT_MULTI: /* Multicast-Capable */ break; case POETT_HURL: /* Host-URL */ str = "URL"; break; case POETT_MOTM: /* Message-Of-The-Minute */ str = "Mesg"; break; case POETT_RTEADD: /* IP-Route-Add */ break; } switch (ttyp) { case POETT_NAMERR: /* Service-Name-Error */ case POETT_SYSERR: /* AC-System-Error */ if (tlen > 0 && *dp == '\0') tlen = 0; /* FALLTHROUGH */ case POETT_SERVICE: /* Service-Name */ case POETT_ACCESS: /* AC-Name */ case POETT_GENERR: /* Generic-Error */ case POETT_MOTM: /* Message-Of-The-Minute */ case POETT_HURL: /* Host-URL */ (void) fprintf(out, "%s:\"%.*s\" ", str, tlen, dp); break; case POETT_UNIQ: /* Host-Uniq */ case POETT_COOKIE: /* AC-Cookie */ case POETT_RELAY: /* Relay-Session-Id */ (void) fprintf(out, "%s:", str); while (--tlen >= 0) (void) fprintf(out, "%02X", *dp++); (void) putc(' ', out); break; case POETT_VENDOR: /* Vendor-Specific */ (void) fputs("Vendor:", out); if (tlen >= 4) { if (*dp++ != 0) { (void) fprintf(out, "(%02X?)", dp[-1]); } (void) fprintf(out, "%x-%x-%x:", dp[0], dp[1], dp[2]); tlen -= 4; dp += 3; } while (--tlen >= 0) (void) fprintf(out, "%02X", *dp++); (void) putc(' ', out); break; case POETT_MULTI: /* Multicast-Capable */ (void) fprintf(out, "Multi:%d ", *dp); break; case POETT_RTEADD: /* IP-Route-Add */ if (tlen != sizeof (poer)) { (void) fprintf(out, "RTE%d? ", tlen); break; } (void) memcpy(&poer, dp, sizeof (poer)); (void) fputs("RTE:", out); if (poer.poer_dest_network == 0) (void) fputs("default", out); else (void) fputs(ihost(poer.poer_dest_network), out); mask = ntohl(poer.poer_subnet_mask); if (mask != 0 && mask != (uint32_t)~0) { if ((~mask & (~mask + 1)) == 0) (void) fprintf(out, "/%d", sizeof (struct in_addr) * NBBY + 1 - ffs(mask)); else (void) fprintf(out, "/%s", ihost(poer.poer_subnet_mask)); } (void) fprintf(out, ",%s,%u ", ihost(poer.poer_gateway), ntohl(poer.poer_metric)); break; default: (void) fprintf(out, "%s:%d ", poe_tagname(ttyp), tlen); break; } tagp = POET_NEXT(tagp); } (void) putc('\n', out); } /* * Transmit a PPPoE message to the indicated destination. Used for * PADI and PADR messages. */ static int send_pppoe(const poep_t *poep, const char *msgname, const ppptun_atype *destaddr) { struct strbuf ctrl; struct strbuf data; struct ppptun_control *ptc; /* Set up the control data expected by the driver. */ ptc = (struct ppptun_control *)pkt_octl; (void) memset(ptc, '\0', sizeof (*ptc)); ptc->ptc_discrim = PPPOE_DISCRIM; ptc->ptc_action = PTCA_CONTROL; ptc->ptc_address = *destaddr; ctrl.len = sizeof (*ptc); ctrl.buf = (caddr_t)ptc; data.len = poe_length(poep) + sizeof (*poep); data.buf = (caddr_t)poep; if (verbose) logerr("%s: Sending %s to %s: %d bytes\n", myname, msgname, ehost(destaddr), data.len); if (putmsg(tunfd, &ctrl, &data, 0) < 0) { logstrerror("putmsg"); return (-1); } return (0); } /* * Create and transmit a PPPoE Active Discovery Initiation packet. * This is broadcasted to all hosts on the LAN. */ static int send_padi(int localid) { poep_t *poep; ppptun_atype destaddr; poep = poe_mkheader(pkt_output, POECODE_PADI, 0); (void) poe_add_str(poep, POETT_SERVICE, service); (void) poe_add_long(poep, POETT_UNIQ, localid); (void) memset(&destaddr, '\0', sizeof (destaddr)); (void) memcpy(destaddr.pta_pppoe.ptma_mac, ether_bcast, sizeof (destaddr.pta_pppoe.ptma_mac)); return (send_pppoe(poep, "PADI", &destaddr)); } /* * This is used by the procedure below -- when the alarm goes off, * just exit. (This was once a dummy procedure and used the EINTR * side-effect to terminate the loop, but that's not reliable, since * the EINTR could be caught and ignored by the calls to standard * output.) */ /* ARGSUSED */ static void alarm_hand(int dummy) { exit(0); } /* * Send out a single PADI and listen for servers. This implements the * "inquiry" (-i) mode. */ static void find_all_servers(int localid) { struct strbuf ctrl; struct strbuf data; poep_t *poep; int flags; struct sigaction act; struct ppptun_control *ptc; /* Set a default 3-second timer */ (void) memset(&act, '\0', sizeof (act)); act.sa_handler = alarm_hand; (void) sigaction(SIGALRM, &act, NULL); (void) alarm((pado_wait_time + 999) / 1000); /* Broadcast a single request. */ if (send_padi(localid) != 0) return; /* Loop over responses and print them. */ for (;;) { ctrl.maxlen = PKT_OCTL_LEN; ctrl.buf = (caddr_t)pkt_octl; data.maxlen = PKT_INPUT_LEN; data.buf = (caddr_t)pkt_input; flags = 0; if (pppoec_getmsg(tunfd, &ctrl, &data, &flags) < 0) break; /* Ignore unwanted responses from the driver. */ if (ctrl.len != sizeof (*ptc)) { if (verbose) logerr("%s: unexpected %d byte" " control message from driver.\n", myname, ctrl.len); continue; } ptc = (struct ppptun_control *)pkt_octl; poep = (poep_t *)pkt_input; /* If it's an offer, then print it out. */ if (poe_code(poep) == POECODE_PADO) { display_pppoe(stdout, poep, data.len, &ptc->ptc_address); } } } /* * Parse a server filter from the command line. The passed-in string * must be allocated and unchanged, since a pointer to it is saved in * the filter data structure. The string is also parsed for a MAC * address, if possible. */ static void parse_filter(const char *str, int exceptflag) { struct server_filter *sfnew; const char *cp; const char *wordstart; const char *wordend; int len; char hbuf[MAXHOSTNAMELEN]; uchar_t *ucp; uchar_t *mcp; /* Allocate the new filter structure. */ sfnew = (struct server_filter *)calloc(1, sizeof (*sfnew)); if (sfnew == NULL) { logstrerror("filter allocation"); exit(1); } /* Save the string for AC-Name comparison. */ sfnew->sf_name = str; sfnew->sf_isexcept = exceptflag == 0 ? 0 : 1; /* Extract just one word. */ cp = str; while (isspace(*cp)) cp++; wordstart = cp; while (*cp != '\0' && !isspace(*cp)) cp++; wordend = cp; if ((len = wordend - wordstart) >= sizeof (hbuf)) len = sizeof (hbuf) - 1; (void) strlcpy(hbuf, wordstart, len); hbuf[len] = '\0'; /* Try to translate this as an Ethernet host or address. */ mcp = sfnew->sf_mask.ether_addr_octet; if (ether_hostton(hbuf, &sfnew->sf_mac) == 0) { mcp[0] = mcp[1] = mcp[2] = mcp[3] = mcp[4] = mcp[5] = 0xFF; sfnew->sf_hasmac = 1; } else { ucp = sfnew->sf_mac.ether_addr_octet; len = wordend - wordstart; cp = wordstart; while (cp < wordend) { if (ucp >= sfnew->sf_mac.ether_addr_octet + sizeof (sfnew->sf_mac)) break; if (*cp == '*') { *mcp++ = *ucp++ = 0; cp++; } else { if (!isxdigit(*cp)) break; *ucp = hexdecode(*cp++); if (cp < wordend && isxdigit(*cp)) { *ucp = (*ucp << 4) | hexdecode(*cp++); } ucp++; *mcp++ = 0xFF; } if (cp < wordend) { if (*cp != ':' || cp + 1 == wordend) break; cp++; } } if (cp >= wordend) sfnew->sf_hasmac = 1; else if (verbose) logerr("%s: treating '%.*s' as server " "name only, not MAC address\n", myname, len, wordstart); } /* Add to end of list. */ if (sftail == NULL) sfhead = sfnew; else sftail->sf_next = sfnew; sftail = sfnew; } /* * Create a copy of a given PPPoE message. This is used for enqueuing * received PADO (offers) from possible servers. */ static poemsg_t * save_message(const poemsg_t *pmsg) { poemsg_t *newmsg; char *cp; newmsg = (poemsg_t *)malloc(sizeof (*pmsg) + pmsg->poemsg_len + strlen(pmsg->poemsg_iname) + 1); if (newmsg != NULL) { newmsg->poemsg_next = NULL; newmsg->poemsg_data = (const poep_t *)(newmsg + 1); (void) memcpy(newmsg + 1, pmsg->poemsg_data, pmsg->poemsg_len); newmsg->poemsg_len = pmsg->poemsg_len; cp = (char *)newmsg->poemsg_data + pmsg->poemsg_len; newmsg->poemsg_iname = (const char *)cp; (void) strcpy(cp, pmsg->poemsg_iname); (void) memcpy(&newmsg->poemsg_sender, &pmsg->poemsg_sender, sizeof (newmsg->poemsg_sender)); } return (newmsg); } /* * Create and send a PPPoE Active Discovery Request (PADR) message to * the sender of the given PADO. Some tags -- Service-Name, * AC-Cookie, and Relay-Session-Id -- must be copied from PADO to * PADR. Others are not. The Service-Name must be selected from the * offered services in the PADO based on the user's requested service * name. If the server offered "wildcard" service, then we ask for * this only if we can't find the user's requested service. * * Returns 1 if we can't send a valid PADR in response to the given * PADO. The offer should be ignored and the next one tried. */ static int send_padr(poesm_t *psm, const poemsg_t *pado) { poep_t *poep; boolean_t haswild; boolean_t hassvc; const uint8_t *tagp; int ttyp; int tlen; /* * Increment sequence number for PADR so that we don't mistake * old replies for valid ones if the server is very slow. */ psm->poesm_sequence++; poep = poe_mkheader(pkt_output, POECODE_PADR, 0); (void) poe_two_longs(poep, POETT_UNIQ, psm->poesm_localid, psm->poesm_sequence); haswild = B_FALSE; hassvc = B_FALSE; tagp = (const uint8_t *)(pado->poemsg_data + 1); while (poe_tagcheck(pado->poemsg_data, pado->poemsg_len, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; tlen = POET_GET_LENG(tagp); switch (ttyp) { case POETT_SERVICE: /* Service-Name */ /* Allow only one */ if (hassvc) break; if (tlen == 0) { haswild = B_TRUE; break; } if (service[0] == '\0' || (tlen == strlen(service) && memcmp(service, POET_DATA(tagp), tlen) == 0)) { (void) poe_tag_copy(poep, tagp); hassvc = B_TRUE; } break; /* Ones we should discard */ case POETT_ACCESS: /* AC-Name */ case POETT_UNIQ: /* Host-Uniq */ case POETT_NAMERR: /* Service-Name-Error */ case POETT_SYSERR: /* AC-System-Error */ case POETT_GENERR: /* Generic-Error */ case POETT_HURL: /* Host-URL */ case POETT_MOTM: /* Message-Of-The-Minute */ case POETT_RTEADD: /* IP-Route-Add */ case POETT_VENDOR: /* Vendor-Specific */ case POETT_MULTI: /* Multicast-Capable */ default: /* Anything else we don't understand */ break; /* Ones we should copy */ case POETT_COOKIE: /* AC-Cookie */ case POETT_RELAY: /* Relay-Session-Id */ (void) poe_tag_copy(poep, tagp); break; } tagp = POET_NEXT(tagp); } if (!hassvc) { if (haswild && service[0] == '\0') (void) poe_add_str(poep, POETT_SERVICE, ""); else return (1); } return (send_pppoe(poep, "PADR", &pado->poemsg_sender)); } /* * ******************************************************************** * act_* functions implement the actions driven by the state machine * tables. See "action_table" below. * * All action routines must return the next state value. * ******************************************************************** */ /* ARGSUSED */ static int act_none(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { return (nextst); } /* ARGSUSED */ static int act_fail(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { if (verbose) logerr("%s: unrecoverable error\n", myname); return (PCSMS_DEAD); } /* ARGSUSED */ static int act_spadi(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { if (send_padi(psm->poesm_localid) != 0) return (PCSMS_DEAD); /* * If this is the first time, then initialize the retry count * and interval. */ if (psm->poesm_state == PCSMS_DEAD) { psm->poesm_count = 3; psm->poesm_interval = pado_wait_time; } else { if ((psm->poesm_interval <<= 1) > RESTART_LIMIT) psm->poesm_interval = RESTART_LIMIT; } psm->poesm_timer = psm->poesm_interval; (void) gettimeofday(&tvstart, NULL); return (nextst); } /* ARGSUSED */ static int act_add(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { pmsg = save_message(pmsg); if (pmsg != NULL) { if (psm->poesm_lastoff == NULL) psm->poesm_firstoff = pmsg; else psm->poesm_lastoff->poemsg_next = pmsg; psm->poesm_lastoff = pmsg; } return (nextst); } /* ARGSUSED */ static int act_spadr(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { poemsg_t *msgp; int retv; for (;;) { if ((msgp = psm->poesm_firstoff) == NULL) return (PCSMS_DEAD); retv = send_padr(psm, msgp); if (retv < 0) return (PCSMS_DEAD); if (retv == 0) break; /* Can't send this request; try looking at next offer. */ psm->poesm_firstoff = msgp->poemsg_next; msgp->poemsg_next = psm->poesm_tried; psm->poesm_tried = msgp; } if (psm->poesm_state != PCSMS_REQSENT) { psm->poesm_count = 3; psm->poesm_interval = pads_wait_time; } else { if ((psm->poesm_interval <<= 1) > RESTART_LIMIT) psm->poesm_interval = RESTART_LIMIT; } psm->poesm_timer = psm->poesm_interval; (void) gettimeofday(&tvstart, NULL); return (nextst); } /* ARGSUSED */ static int act_spadrp(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { int retv; retv = send_padr(psm, pmsg); if (retv < 0) return (PCSMS_DEAD); pmsg = save_message(pmsg); if (retv > 0) { /* * Cannot use this one; mark as tried and continue as * if we never saw it. */ pmsg->poemsg_next = psm->poesm_tried; psm->poesm_tried = pmsg; return (psm->poesm_state); } pmsg->poemsg_next = psm->poesm_firstoff; psm->poesm_firstoff = pmsg; if (psm->poesm_lastoff == NULL) psm->poesm_lastoff = pmsg; psm->poesm_count = 3; psm->poesm_timer = psm->poesm_interval = pads_wait_time; (void) gettimeofday(&tvstart, NULL); return (nextst); } /* ARGSUSED */ static int act_spadrn(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { poemsg_t *msgp; int retv; if ((msgp = psm->poesm_firstoff) == NULL) return (PCSMS_DEAD); do { psm->poesm_firstoff = msgp->poemsg_next; msgp->poemsg_next = psm->poesm_tried; psm->poesm_tried = msgp; if ((msgp = psm->poesm_firstoff) == NULL) return (PCSMS_DEAD); retv = send_padr(psm, msgp); if (retv < 0) return (PCSMS_DEAD); } while (retv != 0); psm->poesm_count = 3; psm->poesm_timer = psm->poesm_interval = pads_wait_time; (void) gettimeofday(&tvstart, NULL); return (nextst); } /* * For safety -- remove end of line from strings passed back to pppd. */ static void remove_eol(char *str, size_t len) { while (len > 0) { if (*str == '\n') *str = '$'; str++; len--; } } /* ARGSUSED */ static int act_open(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) { struct ppptun_peer ptp; union ppptun_name ptn; const char *cp; FILE *fp; const uint8_t *tagp, *vp; int tlen, ttyp; char *access; uint32_t val; size_t acc_len, serv_len; /* * The server has now assigned its session ID for the data * (PPP) portion of this tunnel. Send that ID down to the * driver. */ (void) memset(&ptp, '\0', sizeof (ptp)); ptp.ptp_lsessid = psm->poesm_localid; ptp.ptp_rsessid = poe_session_id(pmsg->poemsg_data); (void) memcpy(&ptp.ptp_address, &pmsg->poemsg_sender, sizeof (ptp.ptp_address)); ptp.ptp_style = PTS_PPPOE; if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) { logstrerror("PPPTUN_SPEER"); return (PCSMS_DEAD); } /* * Data communication is now possible on this session. * Connect the data portion to the correct lower stream. */ if ((cp = strchr(pmsg->poemsg_iname, ':')) == NULL) cp = pmsg->poemsg_iname + strlen(pmsg->poemsg_iname); (void) snprintf(ptn.ptn_name, sizeof (ptn.ptn_name), "%.*s:pppoe", cp - pmsg->poemsg_iname, pmsg->poemsg_iname); if (strioctl(tunfd, PPPTUN_SDATA, &ptn, sizeof (ptn), 0) < 0) { logerr("%s: PPPTUN_SDATA %s: %s\n", myname, ptn.ptn_name, mystrerror(errno)); return (PCSMS_DEAD); } if (verbose) logerr("%s: Connection open; session %04X on " "%s\n", myname, ptp.ptp_rsessid, ptn.ptn_name); /* * Walk through the PADS message to get the access server name * and the service. If there are multiple instances of either * tag, then take the last access server and the first * non-null service. */ access = ""; acc_len = 0; serv_len = strlen(service); tagp = (const uint8_t *)(pmsg->poemsg_data + 1); while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; tlen = POET_GET_LENG(tagp); if (ttyp == POETT_ACCESS) { access = (char *)POET_DATA(tagp); acc_len = tlen; } if (serv_len == 0 && ttyp == POETT_SERVICE && tlen != 0) { service = (char *)POET_DATA(tagp); serv_len = tlen; } tagp = POET_NEXT(tagp); } /* * Remove end of line to make sure that integrity of values * passed back to pppd can't be compromised by the PPPoE * server. */ remove_eol(service, serv_len); remove_eol(access, acc_len); /* * pppd has given us a pipe as fd 3, and we're expected to * write out the values of the following environment * variables: * IF_AND_SERVICE * SERVICE_NAME * AC_NAME * AC_MAC * SESSION_ID * VENDOR_SPECIFIC_1 ... N * See usr.bin/pppd/plugins/pppoe.c for more information. */ if ((fp = fdopen(3, "w")) != NULL) { (void) fprintf(fp, "%.*s:%.*s\n", cp - pmsg->poemsg_iname, pmsg->poemsg_iname, serv_len, service); (void) fprintf(fp, "%.*s\n", serv_len, service); (void) fprintf(fp, "%.*s\n", acc_len, access); (void) fprintf(fp, "%s\n", ehost(&pmsg->poemsg_sender)); (void) fprintf(fp, "%d\n", poe_session_id(pmsg->poemsg_data)); tagp = (const uint8_t *)(pmsg->poemsg_data + 1); while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; tlen = POET_GET_LENG(tagp); if (ttyp == POETT_VENDOR && tlen >= 4) { (void) memcpy(&val, POET_DATA(tagp), 4); (void) fprintf(fp, "%08lX:", (unsigned long)ntohl(val)); tlen -= 4; vp = POET_DATA(tagp) + 4; while (--tlen >= 0) (void) fprintf(fp, "%02X", *vp++); (void) putc('\n', fp); } tagp = POET_NEXT(tagp); } (void) fclose(fp); } return (nextst); } static int (* const action_table[PCSMA__MAX])(poesm_t *psm, poemsg_t *pmsg, int event, int nextst) = { act_none, act_fail, act_spadi, act_add, act_spadr, act_spadrp, act_spadrn, act_open }; /* * Dispatch an event and a corresponding message on a given state * machine. */ static void handle_event(poesm_t *psm, int event, poemsg_t *pmsg) { int nextst; if (verbose) logerr("%s: PPPoE Event %s (%d) in state %s " "(%d): action %s (%d)\n", myname, poe_event(event), event, poe_state(psm->poesm_state), psm->poesm_state, poe_action(client_action[psm->poesm_state][event]), client_action[psm->poesm_state][event]); nextst = (*action_table[client_action[psm->poesm_state][event]])(psm, pmsg, event, client_next_state[psm->poesm_state][event]); if (verbose) logerr("%s: PPPoE State change %s (%d) -> %s (%d)\n", myname, poe_state(psm->poesm_state), psm->poesm_state, poe_state(nextst), nextst); psm->poesm_state = nextst; /* * Life-altering states are handled here. If we reach dead * state again after starting, then we failed. If we reach * conversational state, then we're open. */ if (nextst == PCSMS_DEAD) { if (verbose) logerr("%s: action failed\n", myname); exit(1); } if (nextst == PCSMS_CONVERS) { if (verbose) logerr("%s: connected\n", myname); exit(0); } } /* * Check for error message tags in the PPPoE packet. We must ignore * offers that merely report errors, and need to log errors in any * case. */ static int error_check(poemsg_t *pmsg) { const uint8_t *tagp; int ttyp; tagp = (const uint8_t *)(pmsg->poemsg_data + 1); while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; if (ttyp == POETT_NAMERR || ttyp == POETT_SYSERR || ttyp == POETT_GENERR) { if (verbose) display_pppoe(stderr, pmsg->poemsg_data, pmsg->poemsg_len, &pmsg->poemsg_sender); return (-1); } tagp = POET_NEXT(tagp); } return (0); } /* * Extract sequence number, if any, from PADS message, so that we can * relate it to the PADR that we sent. */ static uint32_t get_sequence(const poemsg_t *pmsg) { const uint8_t *tagp; int ttyp; uint32_t vals[2]; tagp = (const uint8_t *)(pmsg->poemsg_data + 1); while (poe_tagcheck(pmsg->poemsg_data, pmsg->poemsg_len, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; if (ttyp == POETT_UNIQ) { if (POET_GET_LENG(tagp) < sizeof (vals)) break; (void) memcpy(vals, POET_DATA(tagp), sizeof (vals)); return (ntohl(vals[1])); } tagp = POET_NEXT(tagp); } return (0); } /* * Server filter cases: * * No filters -- all servers generate RPADO+ event; select the * first responding server. * * Only "except" filters -- matching servers are RPADO, others * are RPADO+. * * Mix of filters -- those matching "pass" are RPADO+, those * matching "except" are RPADO, and all others are also RPADO. * * If the "only" keyword was given, then RPADO becomes -1; only RPADO+ * events occur. */ static int use_server(poemsg_t *pado, const ppptun_atype *pap) { struct server_filter *sfp; const uchar_t *sndp; const uchar_t *macp; const uchar_t *maskp; int i; int passmatched; int tlen; const uint8_t *tagp; int ttyp; /* * If no service mentioned in offer, then we can't use it. */ tagp = (const uint8_t *)(pado->poemsg_data + 1); ttyp = POETT_END; while (poe_tagcheck(pado->poemsg_data, pado->poemsg_len, tagp)) { ttyp = POET_GET_TYPE(tagp); if (ttyp == POETT_END) break; if (ttyp == POETT_SERVICE) { /* * If the user has requested a specific service, then * this selection is exclusive. We never use the * wildcard for this. */ tlen = POET_GET_LENG(tagp); if (service[0] == '\0' || (strlen(service) == tlen && memcmp(service, POET_DATA(tagp), tlen) == 0)) break; /* just in case we run off the end */ ttyp = POETT_END; } tagp = POET_NEXT(tagp); } if (ttyp != POETT_SERVICE) { if (verbose) logerr("%s: Discard unusable offer from %s; service " "'%s' not seen\n", myname, ehost(pap), service); return (-1); } passmatched = 0; for (sfp = sfhead; sfp != NULL; sfp = sfp->sf_next) { passmatched |= !sfp->sf_isexcept; if (sfp->sf_hasmac) { sndp = pado->poemsg_sender.pta_pppoe.ptma_mac; macp = sfp->sf_mac.ether_addr_octet; maskp = sfp->sf_mask.ether_addr_octet; i = sizeof (pado->poemsg_sender.pta_pppoe.ptma_mac); for (; i > 0; i--) if (((*macp++ ^ *sndp++) & *maskp++) != 0) break; if (i <= 0) break; } } if (sfp == NULL) { /* * No match encountered; if only exclude rules have * been seen, then accept this offer. */ if (!passmatched) return (PCSME_RPADOP); } else { if (!sfp->sf_isexcept) return (PCSME_RPADOP); } if (onlyflag) { if (verbose) logerr("%s: Discard unusable offer from %s; server not " "matched\n", myname, ehost(pap)); return (-1); } return (PCSME_RPADO); } /* * This is the normal event loop. It initializes the state machine * and sends in an Open event to kick things off. Then it drops into * a loop to dispatch events for the state machine. */ static void find_server(int localid) { poesm_t psm; struct pollfd pfd[1]; struct timeval tv, tvnow; int retv; poemsg_t pmsg; struct strbuf ctrl; struct strbuf data; poep_t *poep; int flags; uint32_t seqval; struct ppptun_control *ptc; (void) memset(&psm, '\0', sizeof (psm)); /* * Initialize the sequence number with something handy. It * doesn't need to be absolutely unique, since the localid * value actually demultiplexes everything. This just makes * the operation a little safer. */ psm.poesm_sequence = getpid() << 16; psm.poesm_localid = localid; /* Start the state machine */ handle_event(&psm, PCSME_OPEN, NULL); /* Enter event polling loop. */ pfd[0].fd = tunfd; pfd[0].events = POLLIN; for (;;) { /* Wait for timeout or message */ retv = poll(pfd, 1, psm.poesm_timer > 0 ? psm.poesm_timer : INFTIM); if (retv < 0) { logstrerror("poll"); break; } /* Handle a timeout */ if (retv == 0) { psm.poesm_timer = 0; handle_event(&psm, --psm.poesm_count > 0 ? PCSME_TOP : PCSME_TOM, NULL); continue; } /* Adjust the timer for the time we slept. */ if (psm.poesm_timer > 0) { (void) gettimeofday(&tvnow, NULL); tv = tvnow; if ((tv.tv_sec -= tvstart.tv_sec) < 0) { /* Darn */ tv.tv_sec = 1; tv.tv_usec = 0; } else if ((tv.tv_usec -= tvstart.tv_usec) < 0) { tv.tv_usec += 1000000; if (--tv.tv_sec < 0) tv.tv_sec = 0; } psm.poesm_timer -= tv.tv_sec*1000 + tv.tv_usec/1000; tvstart = tvnow; } /* Read in the message from the server. */ ctrl.maxlen = PKT_OCTL_LEN; ctrl.buf = (caddr_t)pkt_octl; data.maxlen = PKT_INPUT_LEN; data.buf = (caddr_t)pkt_input; flags = 0; if (pppoec_getmsg(tunfd, &ctrl, &data, &flags) < 0) break; if (ctrl.len != sizeof (*ptc)) { if (verbose) logerr("%s: discard: ctrl len %d\n", myname, ctrl.len); continue; } poep = (poep_t *)pkt_input; (void) memset(&pmsg, '\0', sizeof (pmsg)); pmsg.poemsg_next = NULL; pmsg.poemsg_data = poep; pmsg.poemsg_len = data.len; ptc = (struct ppptun_control *)pkt_octl; if (ptc->ptc_action != PTCA_CONTROL) { if (verbose) logerr("%s: discard: unexpected action %d\n", myname, ptc->ptc_action); continue; } pmsg.poemsg_iname = ptc->ptc_name; if (verbose) logerr("%s: Received %s from %s/%s\n", myname, poe_codename(poep->poep_code), ehost(&ptc->ptc_address), pmsg.poemsg_iname); pmsg.poemsg_sender = ptc->ptc_address; /* Check for messages from unexpected peers. */ if ((poep->poep_code == POECODE_PADT || poep->poep_code == POECODE_PADS) && (psm.poesm_firstoff == NULL || memcmp(&psm.poesm_firstoff->poemsg_sender, &pmsg.poemsg_sender, sizeof (pmsg.poemsg_sender)) != 0)) { if (verbose) { logerr("%s: Unexpected peer %s", myname, ehost(&ptc->ptc_address)); logerr(" != %s\n", ehost(&psm.poesm_firstoff->poemsg_sender)); } continue; } /* Eliminate stale PADS responses. */ if (poep->poep_code == POECODE_PADS) { seqval = get_sequence(&pmsg); if (seqval != psm.poesm_sequence) { if (verbose) { if (seqval == 0) logerr( "%s: PADS has no sequence " "number.\n", myname); else logerr( "%s: PADS has sequence " "%08X instead of %08X.\n", myname, seqval, psm.poesm_sequence); } continue; } } /* Dispatch message event. */ retv = error_check(&pmsg); switch (poep->poep_code) { case POECODE_PADT: handle_event(&psm, PCSME_RPADT, &pmsg); break; case POECODE_PADS: /* * Got a PPPoE Active Discovery Session- * confirmation message. It's a PADS event if * everything's in order. It's a PADS- event * if the message is merely reporting an * error. */ handle_event(&psm, retv != 0 ? PCSME_RPADSN : PCSME_RPADS, &pmsg); break; case POECODE_PADO: /* Ignore offers that merely report errors. */ if (retv != 0) break; /* Ignore offers from servers we don't want. */ if ((retv = use_server(&pmsg, &ptc->ptc_address)) < 0) break; /* Dispatch either RPADO or RAPDO+ event. */ handle_event(&psm, retv, &pmsg); break; default: if (verbose) logerr("%s: Unexpected code %s (%d)\n", myname, poe_codename(poep->poep_code), poep->poep_code); break; } } exit(1); } static void usage(void) { logerr("Usage:\n" "\t%s [-os#] [-v] [ [ [only]]]\n\n" " or\n\n" "\t%s [-o#] [-v] -i \n", myname, myname); exit(1); } /* * In addition to the usual 0-2 file descriptors, pppd will leave fd 3 * open on a pipe to receive the environment variables. See * pppoe_device_pipe() in pppd/plugins/pppoe.c and device_pipe_hook in * pppd/main.c. */ int main(int argc, char **argv) { int inquiry_mode, exceptflag, arg, localid; char *cp; log_to_stderr(LOGLVL_DBG); if ((myname = *argv) == NULL) myname = "pppoec"; inquiry_mode = 0; while ((arg = getopt(argc, argv, "io:s:v")) != EOF) switch (arg) { case 'i': inquiry_mode++; break; case 'v': verbose++; break; case 'o': pado_wait_time = strtol(optarg, &cp, 0); if (pado_wait_time <= 0 || *cp != '\0' || cp == optarg) { logerr("%s: illegal PADO wait time: %s\n", myname, optarg); exit(1); } break; case 's': pads_wait_time = strtol(optarg, &cp, 0); if (pads_wait_time <= 0 || *cp != '\0' || cp == optarg) { logerr("%s: illegal PADS wait time: %s\n", myname, optarg); exit(1); } break; case '?': usage(); } /* Handle inquiry mode. */ if (inquiry_mode) { if (optind != argc-1) usage(); if (pado_wait_time == 0) pado_wait_time = PADI_INQUIRY_DWELL; /* Invoked by user; open the tunnel driver myself. */ tunfd = open(tunnam, O_RDWR | O_NOCTTY); if (tunfd == -1) { logstrerror(tunnam); exit(1); } /* * Set up the control stream for PPPoE negotiation * (set_control), then broadcast a query for all servers * and listen for replies (find_all_servers). */ find_all_servers(set_control(argv[optind])); return (0); } if (pado_wait_time == 0) pado_wait_time = PADI_RESTART_TIME; if (optind >= argc) usage(); /* Make sure we've got a usable tunnel driver on stdin. */ check_stdin(); /* Set up the control stream for PPPoE negotiation. */ localid = set_control(argv[optind++]); /* Pick the service, if any. */ if (optind < argc) service = argv[optind++]; /* Parse out the filters. */ if (optind < argc) { if (strcasecmp(argv[argc - 1], "only") == 0) { argc--; onlyflag = 1; } exceptflag = 0; for (; optind < argc; optind++) { if (!exceptflag && strcasecmp(argv[optind], "except") == 0) { exceptflag = 1; } else { parse_filter(argv[optind], exceptflag); exceptflag = 0; } } } /* Enter the main loop. */ find_server(localid); 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 */ /* * PPPoE Server-mode daemon for use with Solaris PPP 4.0. * * 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 "common.h" #include "pppoed.h" #include "logging.h" static int tunfd; /* Global connection to tunnel device */ char *myname; /* Copied from argv[0] for logging */ static int main_argc; /* Saved for reparse on SIGHUP */ static char **main_argv; /* Saved for reparse on SIGHUP */ static time_t time_started; /* Time daemon was started; for debug */ static time_t last_reread; /* Last time configuration was read. */ /* Various operational statistics. */ static unsigned long input_packets, padi_packets, padr_packets; static unsigned long output_packets; static unsigned long sessions_started; static sigset_t sigmask; /* Global signal mask */ /* * Used for handling errors that occur before we daemonize. */ static void early_error(const char *str) { const char *cp; cp = mystrerror(errno); if (isatty(2)) { (void) fprintf(stderr, "%s: %s: %s\n", myname, str, cp); } else { reopen_log(); logerr("%s: %s", str, cp); } exit(1); } /* * Open the sppptun driver. */ static void open_tunnel_dev(void) { struct ppptun_peer ptp; tunfd = open(tunnam, O_RDWR); if (tunfd == -1) { early_error(tunnam); } /* * Tell the device driver that I'm a daemon handling inbound * connections, not a PPP session. */ (void) memset(&ptp, '\0', sizeof (ptp)); ptp.ptp_style = PTS_PPPOE; ptp.ptp_flags = PTPF_DAEMON; (void) memcpy(ptp.ptp_address.pta_pppoe.ptma_mac, ether_bcast, sizeof (ptp.ptp_address.pta_pppoe.ptma_mac)); if (strioctl(tunfd, PPPTUN_SPEER, &ptp, sizeof (ptp), sizeof (ptp)) < 0) { myperror("PPPTUN_SPEER"); exit(1); } } /* * Callback function for fdwalk. Closes everything but the tunnel * file descriptor when becoming daemon. (Log file must be reopened * manually, since syslog file descriptor, if any, is unknown.) */ /*ARGSUSED*/ static int fdcloser(void *arg, int fd) { if (fd != tunfd) (void) close(fd); return (0); } /* * Become a daemon. */ static void daemonize(void) { pid_t cpid; /* * 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 setsid, * we make certain that we're not the session group leader and * can never reacquire a controlling terminal. */ if ((cpid = fork()) == (pid_t)-1) { early_error("fork 1"); } if (cpid != 0) { (void) wait(NULL); _exit(0); } if (setsid() == (pid_t)-1) { early_error("setsid"); } if ((cpid = fork()) == (pid_t)-1) { early_error("fork 2"); } if (cpid != 0) { /* Parent just exits */ (void) printf("%d\n", (int)cpid); (void) fflush(stdout); _exit(0); } (void) chdir("/"); (void) umask(0); (void) fdwalk(fdcloser, NULL); reopen_log(); } /* * Handle SIGHUP -- close and reopen non-syslog log files and reparse * options. */ /*ARGSUSED*/ static void handle_hup(int sig) { close_log_files(); global_logging(); last_reread = time(NULL); parse_options(tunfd, main_argc, main_argv); } /* * Handle SIGINT -- write current daemon status to /tmp. */ /*ARGSUSED*/ static void handle_int(int sig) { FILE *fp; char dumpname[MAXPATHLEN]; time_t now; struct rusage rusage; (void) snprintf(dumpname, sizeof (dumpname), "/tmp/pppoed.%ld", getpid()); if ((fp = fopen(dumpname, "w+")) == NULL) { logerr("%s: %s", dumpname, mystrerror(errno)); return; } now = time(NULL); (void) fprintf(fp, "pppoed running %s", ctime(&now)); (void) fprintf(fp, "Started on %s", ctime(&time_started)); if (last_reread != 0) (void) fprintf(fp, "Last reconfig %s", ctime(&last_reread)); (void) putc('\n', fp); if (getrusage(RUSAGE_SELF, &rusage) == 0) { (void) fprintf(fp, "CPU usage: user %ld.%06ld, system %ld.%06ld\n", rusage.ru_utime.tv_sec, rusage.ru_utime.tv_usec, rusage.ru_stime.tv_sec, rusage.ru_stime.tv_usec); } (void) fprintf(fp, "Packets: %lu received (%lu PADI, %lu PADR), ", input_packets, padi_packets, padr_packets); (void) fprintf(fp, "%lu transmitted\n", output_packets); (void) fprintf(fp, "Sessions started: %lu\n\n", sessions_started); dump_configuration(fp); (void) fclose(fp); } static void add_signal_handlers(void) { struct sigaction sa; (void) sigemptyset(&sigmask); (void) sigaddset(&sigmask, SIGHUP); (void) sigaddset(&sigmask, SIGCHLD); (void) sigaddset(&sigmask, SIGINT); (void) sigprocmask(SIG_BLOCK, &sigmask, NULL); sa.sa_mask = sigmask; sa.sa_flags = 0; /* Signals to handle */ sa.sa_handler = handle_hup; if (sigaction(SIGHUP, &sa, NULL) < 0) early_error("sigaction HUP"); sa.sa_handler = handle_int; if (sigaction(SIGINT, &sa, NULL) < 0) early_error("sigaction INT"); /* * Signals to ignore. Ignoring SIGCHLD in this way makes the * children exit without ever creating zombies. (No wait(2) * call required.) */ sa.sa_handler = SIG_IGN; if (sigaction(SIGPIPE, &sa, NULL) < 0) early_error("sigaction PIPE"); sa.sa_flags = SA_NOCLDWAIT; if (sigaction(SIGCHLD, &sa, NULL) < 0) early_error("sigaction CHLD"); } /* * Dispatch a message from the tunnel driver. It could be an actual * PPPoE message or just an event notification. */ static void handle_input(uint32_t *ctrlbuf, int ctrllen, uint32_t *databuf, int datalen) { poep_t *poep = (poep_t *)databuf; union ppptun_name ptn; int retv; struct strbuf ctrl; struct strbuf data; void *srvp; boolean_t launch; struct ppptun_control *ptc; if (ctrllen != sizeof (*ptc)) { logdbg("bogus %d byte control message from driver", ctrllen); return; } ptc = (struct ppptun_control *)ctrlbuf; /* Switch out on event notifications. */ switch (ptc->ptc_action) { case PTCA_TEST: logdbg("test reply for discriminator %X", ptc->ptc_discrim); return; case PTCA_CONTROL: break; case PTCA_DISCONNECT: logdbg("session %d disconnected on %s; send PADT", ptc->ptc_rsessid, ptc->ptc_name); poep = poe_mkheader(pkt_output, POECODE_PADT, ptc->ptc_rsessid); ptc->ptc_action = PTCA_CONTROL; ctrl.len = sizeof (*ptc); ctrl.buf = (caddr_t)ptc; data.len = poe_length(poep) + sizeof (*poep); data.buf = (caddr_t)poep; if (putmsg(tunfd, &ctrl, &data, 0) < 0) { logerr("putmsg PADT: %s", mystrerror(errno)); } else { output_packets++; } return; case PTCA_UNPLUMB: logdbg("%s unplumbed", ptc->ptc_name); return; case PTCA_BADCTRL: logwarn("bad control data on %s for session %u", ptc->ptc_name, ptc->ptc_rsessid); return; default: logdbg("unexpected code %d from driver", ptc->ptc_action); return; } /* Only PPPoE control messages get here. */ input_packets++; if (datalen < sizeof (*poep)) { logdbg("incomplete PPPoE message from %s/%s", ehost(&ptc->ptc_address), ptc->ptc_name); return; } /* Server handles only PADI and PADR; all others are ignored. */ if (poep->poep_code == POECODE_PADI) { padi_packets++; } else if (poep->poep_code == POECODE_PADR) { padr_packets++; } else { loginfo("unexpected %s from %s", poe_codename(poep->poep_code), ehost(&ptc->ptc_address)); return; } logdbg("Recv from %s/%s: %s", ehost(&ptc->ptc_address), ptc->ptc_name, poe_codename(poep->poep_code)); /* Parse out service and formulate template reply. */ retv = locate_service(poep, datalen, ptc->ptc_name, &ptc->ptc_address, pkt_output, &srvp); /* Continue formulating reply */ launch = B_FALSE; if (retv != 1) { /* Ignore initiation if we don't offer a service. */ if (retv <= 0 && poep->poep_code == POECODE_PADI) { logdbg("no services; no reply"); return; } if (retv == 0) (void) poe_add_str((poep_t *)pkt_output, POETT_NAMERR, "No such service."); } else { /* Exactly one service chosen; if it's PADR, then we start. */ if (poep->poep_code == POECODE_PADR) { launch = B_TRUE; } } poep = (poep_t *)pkt_output; /* Select control interface for output. */ (void) strncpy(ptn.ptn_name, ptc->ptc_name, sizeof (ptn.ptn_name)); if (strioctl(tunfd, PPPTUN_SCTL, &ptn, sizeof (ptn), 0) < 0) { logerr("PPPTUN_SCTL %s: %s", ptn.ptn_name, mystrerror(errno)); return; } /* Launch the PPP service */ if (launch && launch_service(tunfd, poep, srvp, ptc)) sessions_started++; /* Send the reply. */ ctrl.len = sizeof (*ptc); ctrl.buf = (caddr_t)ptc; data.len = poe_length(poep) + sizeof (*poep); data.buf = (caddr_t)poep; if (putmsg(tunfd, &ctrl, &data, 0) < 0) { logerr("putmsg %s: %s", ptc->ptc_name, mystrerror(errno)); } else { output_packets++; logdbg("Send to %s/%s: %s", ehost(&ptc->ptc_address), ptc->ptc_name, poe_codename(poep->poep_code)); } } static void main_loop(void) { struct strbuf ctrl; struct strbuf data; int flags; int rc; int err; for (;;) { ctrl.maxlen = PKT_OCTL_LEN; ctrl.buf = (caddr_t)pkt_octl; data.maxlen = PKT_INPUT_LEN; data.buf = (caddr_t)pkt_input; /* Allow signals only while idle */ (void) sigprocmask(SIG_UNBLOCK, &sigmask, NULL); errno = 0; flags = 0; rc = mygetmsg(tunfd, &ctrl, &data, &flags); err = errno; /* * Block signals -- data structures must not change * while we're busy dispatching the client's request */ (void) sigprocmask(SIG_BLOCK, &sigmask, NULL); if (rc == -1) { if (err == EAGAIN || err == EINTR) continue; logerr("%s getmsg: %s", tunnam, mystrerror(err)); exit(1); } if (rc > 0) logwarn("%s returned truncated data", tunnam); else handle_input(pkt_octl, ctrl.len, pkt_input, data.len); } } int main(int argc, char **argv) { prog_name = "pppoed"; log_level = 1; /* Default to error messages only at first */ time_started = time(NULL); if ((myname = argv[0]) == NULL) myname = "pppoed"; main_argc = argc; main_argv = argv; open_tunnel_dev(); add_signal_handlers(); daemonize(); parse_options(tunfd, argc, argv); main_loop(); return (0); } /* * 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 */ /* * PPPoE Server-mode daemon option parsing. * * Copyright (c) 2000-2001 by Sun Microsystems, Inc. * All rights reserved. */ #ifndef PPPOED_H #define PPPOED_H #include #include "common.h" #ifdef __cplusplus extern "C" { #endif /* Functions in options.c */ extern void parse_options(int tunfd, int argc, char **argv); extern int locate_service(poep_t *poep, int plen, const char *iname, ppptun_atype *pap, uint32_t *outp, void **srvp); extern int launch_service(int tunfd, poep_t *poep, void *srvp, struct ppptun_control *ptc); extern void dump_configuration(FILE *fp); #ifdef __cplusplus } #endif #endif /* PPPOED_H */ #!/bin/sh # # 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 (c) 1999 by Sun Microsystems, Inc. # All rights reserved. # #ident "%Z%%M% %I% %E% SMI" echo_file usr/src/cmd/cmd-inet/usr.lib/Makefile.lib # # 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 2008 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Copyright (c) 2018, Joyent, Inc. PROG = wpad MANIFEST = wpa.xml OBJS = wpa_supplicant.o wpa.o wpa_enc.o eloop.o \ driver_wifi.o l2_packet.o SRCS = $(OBJS:%.o=%.c) include ../../../Makefile.cmd ROOTMANIFESTDIR = $(ROOTSVCNETWORK) # The wpa_enc.c file is not ported to the OpenSSL 3.x API yet and so we need to # make sure the deprecated 1.x API is fully available for it. Once the # wpa_enc.c file is adapted for OpenSSL 3.x this should be updated or removed. # See also https://www.illumos.org/issues/16917 CPPFLAGS += -DOPENSSL_API_COMPAT=10101 LDLIBS += -ldladm -ldlpi all install : LDLIBS += -lcrypto SMOFF += all_func_returns .KEEP_STATE: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(POST_PROCESS) include ../Makefile.lib install: all $(ROOTLIBINETPROG) $(ROOTMANIFEST) check: $(CHKMANIFEST) clean: $(RM) $(OBJS) include ../../../Makefile.targ WPA Supplicant ============== Copyright (c) 2003-2004, Jouni Malinen All Rights Reserved. Sun elects to license this software under the BSD license. License ------- BSD license: Redistribution and use in source and binary forms, with or without modification, are permitted provided that the following conditions are met: 1. Redistributions of source code must retain the above copyright notice, this list of conditions and the following disclaimer. 2. Redistributions in binary form must reproduce the above copyright notice, this list of conditions and the following disclaimer in the documentation and/or other materials provided with the distribution. 3. Neither the name(s) of the above-listed copyright holder(s) nor the names of its contributors may be used to endorse or promote products derived from this software without specific prior written permission. THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. Features -------- Supported WPA/IEEE 802.11i features: - WPA-PSK ("WPA-Personal") - WPA with EAP (e.g., with RADIUS authentication server) ("WPA-Enterprise") Following authentication methods are supported with an integrate IEEE 802.1X Supplicant: * EAP-TLS * EAP-PEAP/MSCHAPv2 (both PEAPv0 and PEAPv1) * EAP-PEAP/TLS (both PEAPv0 and PEAPv1) * EAP-PEAP/GTC (both PEAPv0 and PEAPv1) * EAP-PEAP/OTP (both PEAPv0 and PEAPv1) * EAP-PEAP/MD5-Challenge (both PEAPv0 and PEAPv1) * EAP-TTLS/EAP-MD5-Challenge * EAP-TTLS/EAP-GTC * EAP-TTLS/EAP-OTP * EAP-TTLS/EAP-MSCHAPv2 * EAP-TTLS/EAP-TLS * EAP-TTLS/MSCHAPv2 * EAP-TTLS/MSCHAP * EAP-TTLS/PAP * EAP-TTLS/CHAP * EAP-SIM * LEAP (note: only with WEP keys, i.e., not for WPA; in addition, LEAP requires special support from the driver for IEEE 802.11 authentication) (following methods are supported, but since they do not generate keying material, they cannot be used with WPA or IEEE 802.1X WEP keying) * EAP-MD5-Challenge * EAP-MSCHAPv2 * EAP-GTC * EAP-OTP Alternatively, an external program, e.g., Xsupplicant, can be used for EAP authentication. - key management for CCMP, TKIP, WEP104, WEP40 - RSN/WPA2 (IEEE 802.11i) * pre-authentication * PMKSA caching Requirements ------------ Current hardware/software requirements: - Linux kernel 2.4.x or 2.6.x - Linux Wireless Extensions v15 or newer - drivers: Host AP driver for Prism2/2.5/3 (development snapshot/v0.2.x) in Managed mode ('iwconfig wlan0 mode managed'). Please note that station firmware version needs to be 1.7.0 or newer to work in WPA mode. Linuxant DriverLoader (http://www.linuxant.com/driverloader/) with Windows NDIS driver for your wlan card supporting WPA. Agere Systems Inc. Linux Driver (http://www.agere.com/support/drivers/) Please note that the driver interface file (driver_hermes.c) and hardware specific include files are not included in the wpa_supplicant distribution. You will need to copy these from the source package of the Agere driver. madwifi driver for cards based on Atheros chip set (ar521x) (http://sourceforge.net/projects/madwifi/) Please note that you will need to modify the wpa_supplicant Makefile to use correct path for madwifi driver root directory (CFLAGS += -I../madwifi/wpa line in Makefile). ATMEL AT76C5XXx driver for USB and PCMCIA cards (http://atmelwlandriver.sourceforge.net/). Linux ndiswrapper (http://ndiswrapper.sourceforge.net/) with Windows NDIS driver. In theory, any driver that supports Linux wireless extensions can be used with IEEE 802.1X (i.e., not WPA) when using ap_scan=0 option in configuration file. wpa_supplicant was designed to be portable for different drivers and operating systems. Hopefully, support for more wlan cards will be added in the future. See developer.txt for more information about the design of wpa_supplicant and porting to other drivers. One main goal is to add full WPA/WPA2 support to Linux wireless extensions to allow new drivers to be supported without having to implement new driver-specific interface code in wpa_supplicant. Optional libraries for layer2 packet processing: - libpcap (tested with 0.7.2, most relatively recent versions assumed to work, this is likely to be available with most distributions, http://tcpdump.org/) - libdnet (tested with v1.4, most versions assumed to work, http://libdnet.sourceforge.net/) These libraries are _not_ used in the default build. Instead, internal Linux specific implementation is used. libpcap/libdnet are more portable and they can be used by modifying Makefile (define USE_DNET_PCAP and link with these libraries). Optional libraries for EAP-TLS, EAP-PEAP, and EAP-TTLS: - openssl (tested with 0.9.7c and 0.9.7d, assumed to work with most relatively recent versions; this is likely to be available with most distributions, http://www.openssl.org/) This library is only needed when EAP-TLS, EAP-PEAP, or EAP-TTLS support is enabled. WPA-PSK mode does not require this or EAPOL/EAP implementation. A configuration file, .config, for compilation is needed to enable IEEE 802.1X/EAPOL and EAP methods. Note that EAP-MD5, EAP-GTC, EAP-OTP, and EAP-MSCHAPV2 cannot be used alone with WPA, so they should only be enabled if testing the EAPOL/EAP state machines. However, there can be used as inner authentication algorithms with EAP-PEAP and EAP-TTLS. See Building and installing section below for more detailed information about the wpa_supplicant build time configuration. WPA --- The original security mechanism of IEEE 802.11 standard was not designed to be strong and has proved to be insufficient for most networks that require some kind of security. Task group I (Security) of IEEE 802.11 working group (http://www.ieee802.org/11/) has worked to address the flaws of the base standard and has in practice completed its work in May 2004. The IEEE 802.11i amendment to the IEEE 802.11 standard was approved in June 2004 and this amendment is likely to be published in July 2004. Wi-Fi Alliance (http://www.wi-fi.org/) used a draft version of the IEEE 802.11i work (draft 3.0) to define a subset of the security enhancements that can be implemented with existing wlan hardware. This is called Wi-Fi Protected Access (WPA). This has now become a mandatory component of interoperability testing and certification done by Wi-Fi Alliance. Wi-Fi provides information about WPA at its web site (http://www.wi-fi.org/OpenSection/protected_access.asp). IEEE 802.11 standard defined wired equivalent privacy (WEP) algorithm for protecting wireless networks. WEP uses RC4 with 40-bit keys, 24-bit initialization vector (IV), and CRC32 to protect against packet forgery. All these choice have proved to be insufficient: key space is too small against current attacks, RC4 key scheduling is insufficient (beginning of the pseudorandom stream should be skipped), IV space is too small and IV reuse makes attacks easier, there is no replay protection, and non-keyed authentication does not protect against bit flipping packet data. WPA is an intermediate solution for the security issues. It uses temporal key integrity protocol (TKIP) to replace WEP. TKIP is a compromise on strong security and possibility to use existing hardware. It still uses RC4 for the encryption like WEP, but with per-packet RC4 keys. In addition, it implements replay protection, keyed packet authentication mechanism (Michael MIC). Keys can be managed using two different mechanisms. WPA can either use an external authentication server (e.g., RADIUS) and EAP just like IEEE 802.1X is using or pre-shared keys without need for additional servers. Wi-Fi calls these "WPA-Enterprise" and "WPA-Personal", respectively. Both mechanisms will generate a master session key for the Authenticator (AP) and Supplicant (client station). WPA implements a new key handshake (4-Way Handshake and Group Key Handshake) for generating and exchanging data encryption keys between the Authenticator and Supplicant. This handshake is also used to verify that both Authenticator and Supplicant know the master session key. These handshakes are identical regardless of the selected key management mechanism (only the method for generating master session key changes). IEEE 802.11i / WPA2 ------------------- The design for parts of IEEE 802.11i that were not included in WPA has finished (May 2004) and this amendment to IEEE 802.11 was approved in June 2004. Wi-Fi Alliance is using the final IEEE 802.11i as a new version of WPA called WPA2. This includes, e.g., support for more robust encryption algorithm (CCMP: AES in Counter mode with CBC-MAC) to replace TKIP and optimizations for handoff (reduced number of messages in initial key handshake, pre-authentication, key caching). Some wireless LAN vendors are already providing support for CCMP in their WPA products. There is no "official" interoperability certification for CCMP and/or mixed modes using both TKIP and CCMP, so some interoperability issues can be expected even though many combinations seem to be working with equipment from different vendors. Certification for WPA2 is likely to start during the second half of 2004. wpa_supplicant -------------- wpa_supplicant is an implementation of the WPA Supplicant component, i.e., the part that runs in the client stations. It implements WPA key negotiation with a WPA Authenticator and EAP authentication with Authentication Server. In addition, it controls the roaming and IEEE 802.11 authentication/association of the wlan driver. wpa_supplicant is designed to be a "daemon" program that runs in the background and acts as the backend component controlling the wireless connection. wpa_supplicant supports separate frontend programs and an example text-based frontend, wpa_cli, is included with wpa_supplicant. Following steps are used when associating with an AP using WPA: - wpa_supplicant requests the kernel driver to scan neighboring BSSes - wpa_supplicant selects a BSS based on its configuration - wpa_supplicant requests the kernel driver to associate with the chosen BSS - If WPA-EAP: integrated IEEE 802.1X Supplicant or external Xsupplicant completes EAP authentication with the authentication server (proxied by the Authenticator in the AP) - If WPA-EAP: master key is received from the IEEE 802.1X Supplicant - If WPA-PSK: wpa_supplicant uses PSK as the master session key - wpa_supplicant completes WPA 4-Way Handshake and Group Key Handshake with the Authenticator (AP) - wpa_supplicant configures encryption keys for unicast and broadcast - normal data packets can be transmitted and received Building and installing ----------------------- In order to be able to build wpa_supplicant, you will first need to select which parts of it will be included. This is done by creating a build time configuration file, .config, in the wpa_supplicant root directory. Configuration options are text lines using following format: CONFIG_