# # 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) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # # Hammerhead: Removed in Phase A: nwamd, netcfgd (NWAM obsolete) SUBDIRS= ipmgmtd include ../../Makefile.cmd all: TARGET= all install: TARGET= install clean: TARGET= clean clobber: TARGET= clobber lint: TARGET= lint .KEEP_STATE: all install clean clobber lint: $(SUBDIRS) $(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 (c) 2010, Oracle and/or its affiliates. All rights reserved. # Copyright 2021 Tintri by DDN, Inc. All rights reserved. # # Needed for ROOTFS_LIBDIR definition include ../../../../lib/Makefile.lib PROG= ipmgmtd OBJS= ipmgmt_main.o ipmgmt_door.o ipmgmt_persist.o ipmgmt_util.o SRCS= $(OBJS:.o=.c) SVCMETHOD= net-ipmgmt MANIFEST= network-ipmgmt.xml CFGFILES= ipadm.conf # Needed for ROOTETC definition include ../../../Makefile.cmd ROOTCFGDIR= $(ROOTETC)/ipadm ROOTCFGFILES= $(CFGFILES:%=$(ROOTCFGDIR)/%) ROOTMANIFESTDIR= $(ROOTSVCNETWORK) $(ROOTCFGFILES) : OWNER= ipadm $(ROOTCFGFILES) : GROUP= sys $(ROOTCFGFILES) : FILEMODE= 644 ROOTCMDDIR= $(ROOTFS_LIBDIR)/inet LDLIBS += -lipadm -lnvpair -lsecdb -lnsl -lumem -lscf # # Instrument ipmgmtd with CTF data to ease debugging. # CTFCONVERT_HOOK = && $(CTFCONVERT_O) CTFMERGE_HOOK = && $(CTFMERGE) -L VERSION -o $@ $(OBJS) $(OBJS) : CFLAGS += $(CTF_FLAGS) .KEEP_STATE: .PARALLEL: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) -o $@ $(OBJS) $(LDLIBS) $(CTFMERGE_HOOK) $(POST_PROCESS) install: $(ROOTCMD) $(ROOTMANIFEST) $(ROOTSVCMETHOD) $(ROOTCFGDIR) \ $(ROOTCFGFILES) check: $(SRCS) $(HEADERS) $(CHKMANIFEST) $(CSTYLE) -cpP $(SRCS) $(HEADERS) $(ROOTCMD): $(PROG) clean: $(RM) $(OBJS) lint: lint_SRCS $(ROOTCFGDIR): $(INS.dir) # Hammerhead: order-only prerequisite so $< is the file, not the directory $(ROOTCFGDIR)/%: % | $(ROOTCFGDIR) $(INS.file) 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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # DO NOT EDIT OR PARSE THIS FILE! # # Use the ipadm(8) command to change the contents of this 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 (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016-2017, Chris Fraire . * Copyright 2021, Tintri by DDN. All rights reserved. * Copyright 2022, Oxide Computer Company. */ /* * Main door handler functions used by ipmgmtd to process the different door * call requests, issued by the library libipadm.so. */ #include #include #include #include #include #include #include #include #include #include #include #include "ipmgmt_impl.h" static void ipmgmt_common_handler(char *, char *, db_wfunc_t *); /* Handler declaration for each door command */ typedef void ipmgmt_door_handler_t(void *argp); static ipmgmt_door_handler_t ipmgmt_getaddr_handler, ipmgmt_getprop_handler, ipmgmt_getif_handler, ipmgmt_initif_handler, ipmgmt_aobjop_handler, ipmgmt_resetaddr_handler, ipmgmt_setif_handler, ipmgmt_resetif_handler, ipmgmt_resetprop_handler, ipmgmt_setaddr_handler, ipmgmt_setprop_handler, ipmgmt_ipmp_update_handler; typedef struct ipmgmt_door_info_s { uint_t idi_cmd; boolean_t idi_set; ipmgmt_door_handler_t *idi_handler; } ipmgmt_door_info_t; /* maps door commands to door handler functions */ static ipmgmt_door_info_t i_ipmgmt_door_info_tbl[] = { { IPMGMT_CMD_SETPROP, B_TRUE, ipmgmt_setprop_handler }, { IPMGMT_CMD_SETIF, B_TRUE, ipmgmt_setif_handler }, { IPMGMT_CMD_SETADDR, B_TRUE, ipmgmt_setaddr_handler }, { IPMGMT_CMD_GETPROP, B_FALSE, ipmgmt_getprop_handler }, { IPMGMT_CMD_GETIF, B_FALSE, ipmgmt_getif_handler }, { IPMGMT_CMD_GETADDR, B_FALSE, ipmgmt_getaddr_handler }, { IPMGMT_CMD_RESETIF, B_TRUE, ipmgmt_resetif_handler }, { IPMGMT_CMD_RESETADDR, B_TRUE, ipmgmt_resetaddr_handler }, { IPMGMT_CMD_RESETPROP, B_TRUE, ipmgmt_resetprop_handler }, { IPMGMT_CMD_INITIF, B_TRUE, ipmgmt_initif_handler }, { IPMGMT_CMD_ADDROBJ_LOOKUPADD, B_TRUE, ipmgmt_aobjop_handler }, { IPMGMT_CMD_ADDROBJ_SETLIFNUM, B_TRUE, ipmgmt_aobjop_handler }, { IPMGMT_CMD_ADDROBJ_ADD, B_TRUE, ipmgmt_aobjop_handler }, { IPMGMT_CMD_AOBJNAME2ADDROBJ, B_FALSE, ipmgmt_aobjop_handler }, { IPMGMT_CMD_LIF2ADDROBJ, B_FALSE, ipmgmt_aobjop_handler }, { IPMGMT_CMD_IPMP_UPDATE, B_FALSE, ipmgmt_ipmp_update_handler}, { 0, 0, NULL }, }; /* * The main server procedure function that gets invoked for any of the incoming * door commands. Inside this function we identify the incoming command and * invoke the right door handler function. */ /* ARGSUSED */ void ipmgmt_handler(void *cookie, char *argp, size_t argsz, door_desc_t *dp, uint_t n_desc) { ipmgmt_door_info_t *infop = NULL; ipmgmt_retval_t retval; int i; uint_t err; ucred_t *cred = NULL; for (i = 0; i_ipmgmt_door_info_tbl[i].idi_cmd != 0; i++) { if (i_ipmgmt_door_info_tbl[i].idi_cmd == ((ipmgmt_arg_t *)(void *)argp)->ia_cmd) { infop = &i_ipmgmt_door_info_tbl[i]; break; } } if (infop == NULL) { ipmgmt_log(LOG_ERR, "Invalid door command specified"); err = EINVAL; goto fail; } /* check for solaris.network.interface.config authorization */ if (infop->idi_set) { uid_t uid; struct passwd pwd; char buf[1024]; if (door_ucred(&cred) != 0) { err = errno; ipmgmt_log(LOG_ERR, "Could not get user credentials."); goto fail; } uid = ucred_getruid(cred); if ((int)uid < 0) { err = errno; ipmgmt_log(LOG_ERR, "Could not get user id."); goto fail; } if (getpwuid_r(uid, &pwd, buf, sizeof (buf)) == NULL) { err = errno; ipmgmt_log(LOG_ERR, "Could not get password entry."); goto fail; } if (chkauthattr(NETWORK_INTERFACE_CONFIG_AUTH, pwd.pw_name) != 1) { err = EPERM; ipmgmt_log(LOG_ERR, "Not authorized for operation."); goto fail; } ucred_free(cred); } /* individual handlers take care of calling door_return */ infop->idi_handler((void *)argp); return; fail: ucred_free(cred); retval.ir_err = err; (void) door_return((char *)&retval, sizeof (retval), NULL, 0); } /* * Handles the door command IPMGMT_CMD_GETPROP. It retrieves the persisted * property value for the given property. */ static void ipmgmt_getprop_handler(void *argp) { ipmgmt_prop_arg_t *pargp = argp; ipmgmt_getprop_rval_t rval, *rvalp = &rval; assert(pargp->ia_cmd == IPMGMT_CMD_GETPROP); rvalp->ir_err = ipmgmt_db_walk(ipmgmt_db_getprop, pargp, IPADM_DB_READ); if (rvalp->ir_err == 0) (void) strlcpy(rvalp->ir_pval, pargp->ia_pval, sizeof (rvalp->ir_pval)); (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0); } /* * Handles the door command IPMGMT_CMD_SETPROP. It persists the property value * for the given property in the DB. */ static void ipmgmt_setprop_handler(void *argp) { ipmgmt_prop_arg_t *pargp = argp; ipmgmt_retval_t rval; ipadm_dbwrite_cbarg_t cb; nvlist_t *nvl = NULL; int err; assert(pargp->ia_cmd == IPMGMT_CMD_SETPROP); if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0) goto fail; if (pargp->ia_module[0] != '\0' && (err = nvlist_add_string(nvl, IPADM_NVP_PROTONAME, pargp->ia_module)) != 0) { goto fail; } if (pargp->ia_ifname[0] != '\0' && (err = nvlist_add_string(nvl, IPADM_NVP_IFNAME, pargp->ia_ifname)) != 0) goto fail; if (pargp->ia_aobjname[0] != '\0' && (err = nvlist_add_string(nvl, IPADM_NVP_AOBJNAME, pargp->ia_aobjname)) != 0) goto fail; if ((err = nvlist_add_string(nvl, pargp->ia_pname, pargp->ia_pval)) != 0) goto fail; cb.dbw_nvl = nvl; cb.dbw_flags = pargp->ia_flags; err = ipmgmt_db_walk(ipmgmt_db_update, &cb, IPADM_DB_WRITE); fail: nvlist_free(nvl); rval.ir_err = err; (void) door_return((char *)&rval, sizeof (rval), NULL, 0); } /* * Helper function for ipmgmt_setaddr_handler(). * It converts the nvlist_t, `nvl', to aobjmap node `nodep'. */ static int i_ipmgmt_nvl2aobjnode(nvlist_t *nvl, ipmgmt_aobjmap_t *nodep) { char *aobjname = NULL, *ifname = NULL; int32_t lnum; nvlist_t *nvladdr; sa_family_t af = AF_UNSPEC; ipadm_addr_type_t addrtype = IPADM_ADDR_NONE; int err = 0; /* * Retrieve all the information needed to build '*nodep' from * nvlist_t nvl. */ if ((err = nvlist_lookup_string(nvl, IPADM_NVP_AOBJNAME, &aobjname)) != 0 || (err = nvlist_lookup_string(nvl, IPADM_NVP_IFNAME, &ifname)) != 0 || (err = nvlist_lookup_int32(nvl, IPADM_NVP_LIFNUM, &lnum)) != 0) { return (err); } if (nvlist_exists(nvl, IPADM_NVP_IPV4ADDR)) { af = AF_INET; addrtype = IPADM_ADDR_STATIC; } else if (nvlist_lookup_nvlist(nvl, IPADM_NVP_DHCP, &nvladdr) == 0) { char *reqhost; af = AF_INET; addrtype = IPADM_ADDR_DHCP; /* * ipmgmt_am_reqhost comes through in `nvl' for purposes of * updating the cached representation, but it is persisted as * a stand-alone DB line; so remove it after copying it. */ if (!nvlist_exists(nvl, IPADM_NVP_REQHOST)) { *nodep->ipmgmt_am_reqhost = '\0'; } else { if ((err = nvlist_lookup_string(nvl, IPADM_NVP_REQHOST, &reqhost)) != 0) return (err); (void) strlcpy(nodep->ipmgmt_am_reqhost, reqhost, sizeof (nodep->ipmgmt_am_reqhost)); (void) nvlist_remove(nvl, IPADM_NVP_REQHOST, DATA_TYPE_STRING); } } else if (nvlist_exists(nvl, IPADM_NVP_IPV6ADDR)) { af = AF_INET6; addrtype = IPADM_ADDR_STATIC; } else if (nvlist_lookup_nvlist(nvl, IPADM_NVP_INTFID, &nvladdr) == 0) { struct sockaddr_in6 sin6 = {0}; uint8_t *addr6; uint32_t plen; uint_t n; af = AF_INET6; addrtype = IPADM_ADDR_IPV6_ADDRCONF; if (nvlist_lookup_uint32(nvladdr, IPADM_NVP_PREFIXLEN, &plen) != 0) return (EINVAL); if (plen != 0) { if (nvlist_lookup_uint8_array(nvladdr, IPADM_NVP_IPNUMADDR, &addr6, &n) != 0) return (EINVAL); bcopy(addr6, &sin6.sin6_addr, n); } nodep->ipmgmt_am_linklocal = B_TRUE; nodep->ipmgmt_am_ifid = sin6; } /* * populate the non-addrtype-specific `*nodep' with retrieved values. */ (void) strlcpy(nodep->am_ifname, ifname, sizeof (nodep->am_ifname)); (void) strlcpy(nodep->am_aobjname, aobjname, sizeof (nodep->am_aobjname)); nodep->am_lnum = lnum; nodep->am_family = af; nodep->am_atype = addrtype; nodep->am_next = NULL; /* * Do not store logical interface number in persistent store as it * takes different value on reboot. So remove it from `nvl'. */ if (nvlist_exists(nvl, IPADM_NVP_LIFNUM)) (void) nvlist_remove(nvl, IPADM_NVP_LIFNUM, DATA_TYPE_INT32); return (0); } /* * Handles the door command IPMGMT_CMD_SETADDR. It adds a new address object * node to the list `aobjmap' and optionally persists the address * information in the DB. */ static void ipmgmt_setaddr_handler(void *argp) { ipmgmt_setaddr_arg_t *sargp = argp; ipmgmt_retval_t rval; ipmgmt_aobjmap_t node = {0}; nvlist_t *nvl = NULL; char *nvlbuf; size_t nvlsize = sargp->ia_nvlsize; uint32_t flags = sargp->ia_flags; int err = 0; nvlbuf = (char *)argp + sizeof (ipmgmt_setaddr_arg_t); if ((err = nvlist_unpack(nvlbuf, nvlsize, &nvl, 0)) != 0) goto ret; if (flags & (IPMGMT_ACTIVE|IPMGMT_INIT)) { if ((err = i_ipmgmt_nvl2aobjnode(nvl, &node)) != 0) goto ret; if (flags & IPMGMT_INIT) node.am_flags = (IPMGMT_ACTIVE|IPMGMT_PERSIST); else node.am_flags = flags & ~IPMGMT_PROPS_ONLY; if ((err = ipmgmt_aobjmap_op(&node, ADDROBJ_ADD)) != 0) goto ret; } if ((flags & IPMGMT_PERSIST) && !(flags & IPMGMT_PROPS_ONLY)) { ipadm_dbwrite_cbarg_t cb; cb.dbw_nvl = nvl; cb.dbw_flags = 0; err = ipmgmt_db_walk(ipmgmt_db_add, &cb, IPADM_DB_WRITE); } ret: nvlist_free(nvl); rval.ir_err = err; (void) door_return((char *)&rval, sizeof (rval), NULL, 0); } /* * Handles the door commands that read or modify the `aobjmap' structure. * * IPMGMT_CMD_ADDROBJ_LOOKUPADD - places a stub address object in `aobjmap' * after ensuring that the namespace is not taken. If required, also * generates an `aobjname' for address object for the library to use. * IPMGMT_CMD_ADDROBJ_ADD - add/update address object in `aobjmap' * IPMGMT_CMD_LIF2ADDROBJ - given a logical interface, return address object * associated with that logical interface. * IPMGMT_CMD_AOBJNAME2ADDROBJ - given an address object name return logical * interface associated with that address object. */ static void ipmgmt_aobjop_handler(void *argp) { ipmgmt_aobjop_arg_t *largp = argp; ipmgmt_retval_t rval; ipmgmt_aobjop_rval_t aobjrval; void *rvalp; size_t rsize; ipmgmt_aobjmap_t node; int err = 0; char *ifname = largp->ia_ifname; char *aobjname = largp->ia_aobjname; int32_t lnum = largp->ia_lnum; sa_family_t af = largp->ia_family; ipadm_addr_type_t atype = largp->ia_atype; ipmgmt_aobjmap_t *head; bzero(&aobjrval, sizeof (aobjrval)); switch (largp->ia_cmd) { case IPMGMT_CMD_ADDROBJ_LOOKUPADD: rsize = sizeof (ipmgmt_aobjop_rval_t); rvalp = &aobjrval; bzero(&node, sizeof (node)); (void) strlcpy(node.am_aobjname, aobjname, sizeof (node.am_aobjname)); (void) strlcpy(node.am_ifname, ifname, sizeof (node.am_ifname)); node.am_family = af; node.am_atype = atype; if (atype == IPADM_ADDR_IPV6_ADDRCONF) { node.ipmgmt_am_linklocal = B_TRUE; } /* no logical number is associated with this addrobj yet */ node.am_lnum = -1; /* The address object is not persisted yet. */ node.am_flags = IPMGMT_ACTIVE; err = ipmgmt_aobjmap_op(&node, ADDROBJ_LOOKUPADD); if (err == 0) { aobjrval.ir_lnum = node.am_lnum; (void) strlcpy(aobjrval.ir_aobjname, node.am_aobjname, sizeof (aobjrval.ir_aobjname)); } break; case IPMGMT_CMD_ADDROBJ_SETLIFNUM: rsize = sizeof (ipmgmt_retval_t); rvalp = &rval; bzero(&node, sizeof (node)); (void) strlcpy(node.am_aobjname, aobjname, sizeof (node.am_aobjname)); (void) strlcpy(node.am_ifname, ifname, sizeof (node.am_ifname)); node.am_family = af; node.am_lnum = lnum; err = ipmgmt_aobjmap_op(&node, ADDROBJ_SETLIFNUM); break; case IPMGMT_CMD_ADDROBJ_ADD: rsize = sizeof (ipmgmt_retval_t); rvalp = &rval; if (aobjname[0] == '\0' || ifname[0] == '\0' || lnum == -1 || af == AF_UNSPEC) { err = EINVAL; break; } bzero(&node, sizeof (node)); (void) strlcpy(node.am_aobjname, aobjname, sizeof (node.am_aobjname)); (void) strlcpy(node.am_ifname, ifname, sizeof (node.am_ifname)); node.am_atype = atype; node.am_lnum = lnum; node.am_family = af; /* The address object is not persisted. */ node.am_flags = IPMGMT_ACTIVE; err = ipmgmt_aobjmap_op(&node, ADDROBJ_ADD); break; case IPMGMT_CMD_AOBJNAME2ADDROBJ: rsize = sizeof (ipmgmt_aobjop_rval_t); rvalp = &aobjrval; bzero(&aobjrval, sizeof (aobjrval)); if (aobjname[0] == '\0') { err = EINVAL; break; } (void) pthread_rwlock_rdlock(&aobjmap.aobjmap_rwlock); head = aobjmap.aobjmap_head; for (; head; head = head->am_next) { if (strcmp(head->am_aobjname, aobjname) != 0) continue; /* * For an auto-configured interface, return * the lifnum that has the link-local on it. * Other logical interfaces were created for * prefixes and dhcpv6 addresses and do not * have am_ifid set. */ if (head->am_atype != IPADM_ADDR_IPV6_ADDRCONF || head->ipmgmt_am_linklocal) { break; } } if (head == NULL) { err = ENOENT; (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock); break; } (void) strlcpy(aobjrval.ir_ifname, head->am_ifname, sizeof (aobjrval.ir_ifname)); aobjrval.ir_lnum = head->am_lnum; aobjrval.ir_family = head->am_family; aobjrval.ir_flags = head->am_flags; aobjrval.ir_atype = head->am_atype; aobjrval.ir_atype_cache = head->am_atype_cache; (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock); break; case IPMGMT_CMD_LIF2ADDROBJ: rsize = sizeof (ipmgmt_aobjop_rval_t); rvalp = &aobjrval; bzero(&aobjrval, sizeof (aobjrval)); if (ifname[0] == '\0') { err = EINVAL; break; } (void) pthread_rwlock_rdlock(&aobjmap.aobjmap_rwlock); head = aobjmap.aobjmap_head; for (; head; head = head->am_next) { if (strcmp(head->am_ifname, ifname) == 0 && head->am_lnum == lnum && head->am_family == af) { break; } } if (head == NULL) { err = ENOENT; (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock); break; } (void) strlcpy(aobjrval.ir_aobjname, head->am_aobjname, sizeof (aobjrval.ir_aobjname)); aobjrval.ir_atype = head->am_atype; aobjrval.ir_flags = head->am_flags; aobjrval.ir_atype_cache = head->am_atype_cache; (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock); break; default: rsize = sizeof (ipmgmt_retval_t); rvalp = &rval; err = EINVAL; } ((ipmgmt_retval_t *)rvalp)->ir_err = err; (void) door_return((char *)rvalp, rsize, NULL, 0); } /* * Given an interface name and family, deletes all the address objects * associated with it. */ void i_ipmgmt_delif_aobjs(char *ifname, sa_family_t af, uint32_t flags) { ipmgmt_aobjmap_t *head, *next, *prev; ipadm_db_op_t db_op; prev = NULL; (void) pthread_rwlock_wrlock(&aobjmap.aobjmap_rwlock); head = aobjmap.aobjmap_head; for (; head; head = next) { next = head->am_next; if (strcmp(head->am_ifname, ifname) != 0 || head->am_family != af) { prev = head; continue; } if (head->am_flags == (IPMGMT_ACTIVE|IPMGMT_PERSIST) && flags == IPMGMT_ACTIVE) { /* * If the addres is present in both active and * persistent store, and if we are performing * a temporary delete, we update the node to * indicate that the address is only present in * persistent store and we proceed. Otherwise * we always delete the node from aobjmap. */ head->am_flags &= ~IPMGMT_ACTIVE; head->am_lnum = -1; db_op = IPADM_DB_WRITE; } else { db_op = IPADM_DB_DELETE; if (prev == NULL) aobjmap.aobjmap_head = next; else prev->am_next = next; } (void) ipmgmt_persist_aobjmap(head, db_op); if (db_op == IPADM_DB_DELETE) free(head); } (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock); } /* * Handles the door command IPMGMT_CMD_SETIF. It persists the interface * information in the DB. */ static void ipmgmt_setif_handler(void *argp) { ipmgmt_retval_t rval; rval.ir_err = ipmgmt_persist_if(argp); (void) door_return((char *)&rval, sizeof (rval), NULL, 0); } /* * Handles the door command IPMGMT_CMD_RESETIF. For the given interface, * deletes all the persisted interface configuration. It also deletes, from * `aobjmap', all the address objects configured on the given interface. */ static void ipmgmt_resetif_handler(void *argp) { ipmgmt_if_arg_t *rargp = argp; ipmgmt_retval_t rval; ipmgmt_if_cbarg_t cbarg; uint32_t flags = rargp->ia_flags; int err = 0; cbarg.cb_family = rargp->ia_family; cbarg.cb_ifname = rargp->ia_ifname; cbarg.cb_ipv4exists = B_TRUE; cbarg.cb_ipv6exists = B_TRUE; if (flags & IPMGMT_PERSIST) err = ipmgmt_db_walk(ipmgmt_db_resetif, &cbarg, IPADM_DB_DELETE); if (flags & IPMGMT_ACTIVE) i_ipmgmt_delif_aobjs(rargp->ia_ifname, rargp->ia_family, flags); rval.ir_err = err; (void) door_return((char *)&rval, sizeof (rval), NULL, 0); } /* * Handles the door command IPMGMT_CMD_RESETADDR. For the given addrobj * deletes all the persisted addrobj configuration. It also deletes the * corresponding node, from `aobjmap'. */ static void ipmgmt_resetaddr_handler(void *argp) { ipmgmt_addr_arg_t *rargp = argp; ipmgmt_retval_t rval; ipmgmt_aobjmap_t node; uint32_t flags = rargp->ia_flags; int err = 0; ipmgmt_resetaddr_cbarg_t cbarg; cbarg.cb_aobjname = rargp->ia_aobjname; if (flags & IPMGMT_PERSIST) err = ipmgmt_db_walk(ipmgmt_db_resetaddr, &cbarg, IPADM_DB_DELETE); if (flags & IPMGMT_ACTIVE) { bzero(&node, sizeof (node)); (void) strlcpy(node.am_aobjname, rargp->ia_aobjname, sizeof (node.am_aobjname)); /* * am_lnum is used only for IPv6 autoconf case, since there * can be multiple nodes with the same aobjname. */ node.am_lnum = rargp->ia_lnum; node.am_flags = flags; (void) ipmgmt_aobjmap_op(&node, ADDROBJ_DELETE); } rval.ir_err = err; (void) door_return((char *)&rval, sizeof (rval), NULL, 0); } /* * Handles the door command IPMGMT_CMD_GETADDR. It retrieves the persisted * address for a given `gargp->ia_aobjname'. If it is not defined then it * retrieves all the addresses configured on `gargp->ia_ifname'. The * "ipadm show-addr addrobj" or "ipadm show-addr /\*" will call this * handler through library. */ static void ipmgmt_getaddr_handler(void *argp) { ipmgmt_getaddr_arg_t *gargp = argp; ipmgmt_common_handler(gargp->ia_ifname, gargp->ia_aobjname, ipmgmt_db_getaddr); } /* * Handles the door command IPMGMT_CMD_RESETPROP. It deletes the property line * from the DB. */ static void ipmgmt_resetprop_handler(void *argp) { ipmgmt_prop_arg_t *pargp = argp; ipmgmt_retval_t rval; assert(pargp->ia_cmd == IPMGMT_CMD_RESETPROP); rval.ir_err = ipmgmt_db_walk(ipmgmt_db_resetprop, pargp, IPADM_DB_DELETE); (void) door_return((char *)&rval, sizeof (rval), NULL, 0); } /* * Handles the door command IPMGMT_CMD_GETIF. It retrieves the names of all * persisted interfaces and the IP protocol families (IPv4 or IPv6) they * support. Returns the info as a nvlist using door_return() from * ipmgmt_common_handler(). */ static void ipmgmt_getif_handler(void *argp) { ipmgmt_getif_arg_t *getif = argp; assert(getif->ia_cmd == IPMGMT_CMD_GETIF); ipmgmt_common_handler(getif->ia_ifname, NULL, ipmgmt_db_getif); } /* * Handles the door command IPMGMT_CMD_INITIF. It retrieves all the persisted * interface configuration (interface properties and addresses), for all those * interfaces that need to be initialized. */ static void ipmgmt_initif_handler(void *argp) { ipmgmt_initif_arg_t *initif = argp; size_t buflen, nvlsize; char *buf = NULL, *onvlbuf, *invlbuf; ipmgmt_get_rval_t rval, *rvalp = &rval; ipmgmt_initif_cbarg_t cbarg; int err; assert(initif->ia_cmd == IPMGMT_CMD_INITIF); bzero(&cbarg, sizeof (cbarg)); invlbuf = (char *)argp + sizeof (ipmgmt_initif_arg_t); nvlsize = initif->ia_nvlsize; err = nvlist_unpack(invlbuf, nvlsize, &cbarg.cb_invl, 0); if (err != 0) goto fail; cbarg.cb_family = initif->ia_family; if (nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0) != 0) goto fail; err = ipmgmt_db_walk(ipmgmt_db_initif, &cbarg, IPADM_DB_READ); if (err == ENOENT && cbarg.cb_ocnt > 0) { /* * If there is at least one entry in the nvlist, * do not return error. */ err = 0; } if (err != 0) goto fail; if ((err = nvlist_size(cbarg.cb_onvl, &nvlsize, NV_ENCODE_NATIVE)) != 0) goto fail; if (nvlsize > (UINT32_MAX - sizeof (ipmgmt_get_rval_t))) goto fail; buflen = nvlsize + sizeof (ipmgmt_get_rval_t); /* * We cannot use malloc() here because door_return never returns, and * memory allocated by malloc() would get leaked. Use alloca() instead. */ buf = alloca(buflen); onvlbuf = buf + sizeof (ipmgmt_get_rval_t); if ((err = nvlist_pack(cbarg.cb_onvl, &onvlbuf, &nvlsize, NV_ENCODE_NATIVE, 0)) != 0) { goto fail; } nvlist_free(cbarg.cb_invl); nvlist_free(cbarg.cb_onvl); rvalp = (ipmgmt_get_rval_t *)(void *)buf; rvalp->ir_err = 0; rvalp->ir_nvlsize = nvlsize; (void) door_return(buf, buflen, NULL, 0); return; fail: nvlist_free(cbarg.cb_invl); nvlist_free(cbarg.cb_onvl); rvalp->ir_err = err; (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0); } int ipmgmt_persist_if(ipmgmt_if_arg_t *sargp) { ipadm_dbwrite_cbarg_t cb; uint32_t flags = sargp->ia_flags; nvlist_t *nvl = NULL; char strval[IPMGMT_STRSIZE]; int err = 0; if (!(flags & IPMGMT_PERSIST) || sargp->ia_family == AF_UNSPEC || sargp->ia_ifname[0] == '\0') { err = EINVAL; goto ret; } if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0) goto ret; if ((err = nvlist_add_string(nvl, IPADM_NVP_IFNAME, sargp->ia_ifname)) != 0) goto ret; if ((err = ipmgmt_update_family_nvp(nvl, sargp->ia_family, IPMGMT_APPEND)) != 0) goto ret; (void) snprintf(strval, IPMGMT_STRSIZE, "%d", sargp->ia_ifclass); if ((err = nvlist_add_string(nvl, IPADM_NVP_IFCLASS, strval)) != 0) goto ret; cb.dbw_nvl = nvl; cb.dbw_flags = IPMGMT_APPEND | IPMGMT_UPDATE_IF; err = ipmgmt_db_walk(ipmgmt_db_update_if, &cb, IPADM_DB_WRITE); ret: nvlist_free(nvl); return (err); } /* * The helper for ipmgmt_getif_handler and ipmgmt_getaddr_handler */ static void ipmgmt_common_handler(char *if_name, char *aobj_name, db_wfunc_t worker) { size_t buflen, onvlsize; char *buf, *onvlbuf; ipmgmt_get_cbarg_t cbarg; ipmgmt_get_rval_t rval, *rvalp = &rval; int err = 0; cbarg.cb_ifname = if_name; cbarg.cb_aobjname = aobj_name; cbarg.cb_ocnt = 0; if (nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0) != 0) goto fail; err = ipmgmt_db_walk(worker, &cbarg, IPADM_DB_READ); if (err == ENOENT && cbarg.cb_ocnt > 0) { /* * If there is at least one entry in the nvlist, * do not return error. */ err = 0; } if (err != 0) goto fail; if ((err = nvlist_size(cbarg.cb_onvl, &onvlsize, NV_ENCODE_NATIVE)) != 0) goto fail; if (onvlsize > (UINT32_MAX - sizeof (ipmgmt_get_rval_t))) goto fail; buflen = onvlsize + sizeof (ipmgmt_get_rval_t); /* * We cannot use malloc() here because door_return never returns, and * memory allocated by malloc() would get leaked. Use alloca() instead. */ buf = alloca(buflen); onvlbuf = buf + sizeof (ipmgmt_get_rval_t); if ((err = nvlist_pack(cbarg.cb_onvl, &onvlbuf, &onvlsize, NV_ENCODE_NATIVE, 0)) != 0) goto fail; nvlist_free(cbarg.cb_onvl); rvalp = (ipmgmt_get_rval_t *)(void *)buf; rvalp->ir_err = 0; rvalp->ir_nvlsize = onvlsize; (void) door_return(buf, buflen, NULL, 0); fail: nvlist_free(cbarg.cb_onvl); rvalp->ir_err = err; (void) door_return((char *)rvalp, sizeof (*rvalp), NULL, 0); } /* * Handles the door command IPMGMT_CMD_IPMP_UPDATE */ static void ipmgmt_ipmp_update_handler(void *argp) { ipmgmt_ipmp_update_arg_t *uargp = argp; ipmgmt_retval_t rval; ipadm_dbwrite_cbarg_t cb; boolean_t gif_exists; char gifname[LIFNAMSIZ]; nvlist_t *nvl = NULL; uint32_t flags = uargp->ia_flags; int err = 0; assert(uargp->ia_cmd == IPMGMT_CMD_IPMP_UPDATE); gif_exists = ipmgmt_persist_if_exists(uargp->ia_gifname, AF_UNSPEC); if (!ipmgmt_persist_if_exists(uargp->ia_mifname, AF_UNSPEC)) { err = EINVAL; goto ret; } ipmgmt_get_group_interface(uargp->ia_mifname, gifname, LIFNAMSIZ); if (flags & IPMGMT_APPEND) { /* Group interface should be available in the DB */ if (!gif_exists) { err = ENOENT; goto ret; } if (gifname[0] != '\0') { err = EEXIST; goto ret; } } if (flags & IPMGMT_REMOVE) { /* We cannot remove something that does not exist */ if (!gif_exists || gifname[0] == '\0') { err = ENOENT; goto ret; } if (strcmp(uargp->ia_gifname, gifname) != 0) { err = EINVAL; goto ret; } } if (flags & IPMGMT_PERSIST) { if ((err = nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0)) != 0) goto ret; if ((err = nvlist_add_string(nvl, IPADM_NVP_IFNAME, uargp->ia_gifname)) != 0) goto ret; if ((err = nvlist_add_string(nvl, IPADM_NVP_MIFNAMES, uargp->ia_mifname)) != 0) goto ret; if ((err = nvlist_add_string(nvl, IPADM_NVP_GIFNAME, uargp->ia_gifname)) != 0) goto ret; cb.dbw_nvl = nvl; cb.dbw_flags = flags | IPMGMT_UPDATE_IF | IPMGMT_UPDATE_IPMP; err = ipmgmt_db_walk(ipmgmt_db_update_if, &cb, IPADM_DB_WRITE); } ret: nvlist_free(nvl); rval.ir_err = err; (void) door_return((char *)&rval, sizeof (rval), NULL, 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) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, Chris Fraire . * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ #ifndef _IPMGMT_IMPL_H #define _IPMGMT_IMPL_H #ifdef __cplusplus extern "C" { #endif #include #include #include #include #include #include #include #define IPMGMT_STRSIZE 256 #define IPMGMTD_FMRI "svc:/network/ip-interface-management:default" /* ipmgmt_door.c */ extern void ipmgmt_handler(void *, char *, size_t, door_desc_t *, uint_t); /* ipmgmt_util.c */ extern void ipmgmt_log(int, const char *, ...); extern int ipmgmt_cpfile(const char *, const char *, boolean_t); /* ipmgmt_persist.c */ extern boolean_t ipmgmt_persist_if_exists(const char *, sa_family_t); extern void ipmgmt_get_group_interface(const char *, char *, size_t); /* * following are the list of DB walker callback functions and the callback * arguments for each of the callback functions used by the daemon */ /* following functions take 'ipmgmt_prop_arg_t' as the callback argument */ extern db_wfunc_t ipmgmt_db_getprop, ipmgmt_db_resetprop; /* following functions take ipadm_dbwrite_cbarg_t as callback argument */ extern db_wfunc_t ipmgmt_db_add, ipmgmt_db_update, ipmgmt_db_update_if; typedef struct { char *cb_ifname; char *cb_aobjname; nvlist_t *cb_onvl; int cb_ocnt; } ipmgmt_get_cbarg_t; extern db_wfunc_t ipmgmt_db_getif; extern db_wfunc_t ipmgmt_db_getaddr; typedef struct { sa_family_t cb_family; char *cb_ifname; boolean_t cb_ipv4exists; boolean_t cb_ipv6exists; } ipmgmt_if_cbarg_t; extern db_wfunc_t ipmgmt_db_setif, ipmgmt_db_resetif; typedef struct { char *cb_aobjname; } ipmgmt_resetaddr_cbarg_t; extern db_wfunc_t ipmgmt_db_resetaddr; typedef struct { sa_family_t cb_family; nvlist_t *cb_invl; nvlist_t *cb_onvl; int cb_ocnt; } ipmgmt_initif_cbarg_t; extern db_wfunc_t ipmgmt_db_initif; /* * A linked list of address object nodes. Each node in the list tracks * following information for the address object identified by `am_aobjname'. * - interface on which the address is created * - logical interface number on which the address is created * - address family * - `am_nextnum' identifies the next number to use to generate user part * of `aobjname'. * - address type (static, dhcp or addrconf) * - `am_flags' indicates if this addrobj in active and/or persist config * - other, ipadm_addr_type_t-specific values are cached in * am_addr_cache (see type ipmgmt_addr_cache_u): * - ipv6: ipmgmt_am_linklocal (macro) * - ipv6: ipmgmt_am_ifid (macro) * - dhcp: ipmgmt_am_reqhost (macro) */ typedef struct ipmgmt_aobjmap_s { struct ipmgmt_aobjmap_s *am_next; char am_aobjname[IPADM_AOBJSIZ]; char am_ifname[LIFNAMSIZ]; int32_t am_lnum; sa_family_t am_family; ipadm_addr_type_t am_atype; uint32_t am_nextnum; uint32_t am_flags; ipmgmt_addr_type_cache_u am_atype_cache; } ipmgmt_aobjmap_t; #define ipmgmt_am_linklocal \ am_atype_cache.ipmgmt_ipv6_cache_s.ipmgmt_linklocal #define ipmgmt_am_ifid \ am_atype_cache.ipmgmt_ipv6_cache_s.ipmgmt_ifid #define ipmgmt_am_reqhost \ am_atype_cache.ipmgmt_dhcp_cache_s.ipmgmt_reqhost /* linked list of `aobjmap' nodes, protected by RW lock */ typedef struct ipmgmt_aobjmap_list_s { ipmgmt_aobjmap_t *aobjmap_head; pthread_rwlock_t aobjmap_rwlock; } ipmgmt_aobjmap_list_t; /* global `aobjmap' defined in ipmgmt_main.c */ extern ipmgmt_aobjmap_list_t aobjmap; /* operations on the `aobjmap' linked list */ #define ADDROBJ_ADD 0x00000001 #define ADDROBJ_DELETE 0x00000002 #define ADDROBJ_LOOKUPADD 0x00000004 #define ADDROBJ_SETLIFNUM 0x00000008 /* Permanent data store for ipadm */ #define IPADM_DB_FILE "/etc/ipadm/ipadm.conf" #define IPADM_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) /* * With the initial integration of the daemon (PSARC 2010/080), the version * of the ipadm data-store (/etc/ipadm/ipadm.conf) was 0. A subsequent fix * needed an upgrade to the data-store and we bumped the version to 1. */ #define IPADM_DB_VERSION 1 /* * A temporary file created in SMF volatile filesystem. This file captures the * in-memory copy of list `aobjmap' on disk. This is done to recover from * daemon reboot (using svcadm) or crashes. */ #define IPADM_TMPFS_DIR "/etc/svc/volatile/ipadm" #define ADDROBJ_MAPPING_DB_FILE IPADM_TMPFS_DIR"/aobjmap.conf" /* * A temporary copy of the ipadm configuration file might need * to be created if write requests are encountered during boottime * and the root filesystem is mounted read-only. */ #define IPADM_VOL_DB_FILE IPADM_TMPFS_DIR"/ipadm.conf" /* SCF resources required to interact with svc.configd */ typedef struct scf_resources { scf_handle_t *sr_handle; scf_instance_t *sr_inst; scf_propertygroup_t *sr_pg; scf_property_t *sr_prop; scf_value_t *sr_val; scf_transaction_t *sr_tx; scf_transaction_entry_t *sr_ent; } scf_resources_t; extern int ipmgmt_update_family_nvp(nvlist_t *, sa_family_t, uint_t); extern int ipmgmt_db_walk(db_wfunc_t *, void *, ipadm_db_op_t); extern int ipmgmt_aobjmap_op(ipmgmt_aobjmap_t *, uint32_t); extern boolean_t ipmgmt_aobjmap_init(void *, nvlist_t *, char *, size_t, int *); extern int ipmgmt_persist_aobjmap(ipmgmt_aobjmap_t *, ipadm_db_op_t); extern boolean_t ipmgmt_ngz_firstboot_postinstall(); extern int ipmgmt_persist_if(ipmgmt_if_arg_t *); extern void ipmgmt_init_prop(); extern boolean_t ipmgmt_db_upgrade(void *, nvlist_t *, char *, size_t, int *); extern int ipmgmt_create_scf_resources(const char *, scf_resources_t *); extern void ipmgmt_release_scf_resources(scf_resources_t *); extern boolean_t ipmgmt_needs_upgrade(scf_resources_t *); extern void ipmgmt_update_dbver(scf_resources_t *); #ifdef __cplusplus } #endif #endif /* _IPMGMT_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 (c) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ /* * The ipmgmtd daemon is started by ip-interface-management SMF service. This * daemon is used to manage, mapping of 'address object' to 'interface name' and * 'logical interface number', on which the address is created. It also provides * a means to update the ipadm persistent data-store. * * The daemon tracks the mapping in-memory using a linked * list `aobjmap'. Access to this list is synchronized using a readers-writers * lock. The active mapping is kept in * /etc/svc/volatile/ipadm/aobjmap.conf cache file, so that the mapping can be * recovered when ipmgmtd exits for some reason (e.g., when ipmgmtd is restarted * using svcadm or accidentally killed). * * Today, the persistent configuration of interfaces, addresses and protocol * properties is kept in /etc/ipadm/ipadm.conf. The access to the persistent * data store is synchronized using reader-writers lock `ipmgmt_dbconf_lock'. * * The communication between the library, libipadm.so and the daemon, is through * doors RPC. The library interacts with the daemon using the commands defined * by `ipmgmt_door_cmd_type_t'. Further any 'write' operation would require * the `NETWORK_INTERFACE_CONFIG_AUTH' authorization. * * On reboot, the aforementioned SMF service starts the daemon before any other * networking service that configures network IP interfaces is started. * Afterwards, the network/physical SMF script instantiates the persisted * network interfaces, interface properties and addresses. */ #include #include #include #include #include #include #include #include #include #include #include "ipmgmt_impl.h" #include #include #include #include #include #include #include const char *progname; /* readers-writers lock for reading/writing daemon data store */ pthread_rwlock_t ipmgmt_dbconf_lock = PTHREAD_RWLOCK_INITIALIZER; /* tracks address object to {ifname|logical number|interface id} mapping */ ipmgmt_aobjmap_list_t aobjmap; /* used to communicate failure to parent process, which spawned the daemon */ static int pfds[2]; /* file descriptor to IPMGMT_DOOR */ static int ipmgmt_door_fd = -1; static void ipmgmt_exit(int); static int ipmgmt_init(); static int ipmgmt_init_privileges(); static void ipmgmt_ngz_persist_if(); static ipadm_handle_t iph; typedef struct ipmgmt_pif_s { struct ipmgmt_pif_s *pif_next; char pif_ifname[LIFNAMSIZ]; boolean_t pif_v4; boolean_t pif_v6; } ipmgmt_pif_t; static ipmgmt_pif_t *ngz_pifs; static int ipmgmt_db_init() { int fd, err, scferr; scf_resources_t res; boolean_t upgrade = B_TRUE; /* * Check to see if we need to upgrade the data-store. We need to * upgrade, if the version of the data-store does not match with * IPADM_DB_VERSION. Further, if we cannot determine the current * version of the data-store, we always err on the side of caution * and upgrade the data-store to current version. */ if ((scferr = ipmgmt_create_scf_resources(IPMGMTD_FMRI, &res)) == 0) upgrade = ipmgmt_needs_upgrade(&res); if (upgrade) { err = ipmgmt_db_walk(ipmgmt_db_upgrade, NULL, IPADM_DB_WRITE); if (err != 0) { ipmgmt_log(LOG_ERR, "could not upgrade the " "ipadm data-store: %s", strerror(err)); err = 0; } else { /* * upgrade was success, let's update SCF with the * current data-store version number. */ if (scferr == 0) ipmgmt_update_dbver(&res); } } if (scferr == 0) ipmgmt_release_scf_resources(&res); /* creates the address object data store, if it doesn't exist */ if ((fd = open(ADDROBJ_MAPPING_DB_FILE, O_CREAT|O_RDONLY, IPADM_FILE_MODE)) == -1) { err = errno; ipmgmt_log(LOG_ERR, "could not open %s: %s", ADDROBJ_MAPPING_DB_FILE, strerror(err)); return (err); } (void) close(fd); aobjmap.aobjmap_head = NULL; (void) pthread_rwlock_init(&aobjmap.aobjmap_rwlock, NULL); /* * If the daemon is recovering from a crash or restart, read the * address object to logical interface mapping and build an in-memory * representation of the mapping. That is, build `aobjmap' structure * from address object data store. */ if ((err = ipadm_rw_db(ipmgmt_aobjmap_init, NULL, ADDROBJ_MAPPING_DB_FILE, 0, IPADM_DB_READ)) != 0) { /* if there was nothing to initialize, it's fine */ if (err != ENOENT) return (err); err = 0; } ipmgmt_ngz_persist_if(); /* create persistent interface info for NGZ */ return (err); } static int ipmgmt_door_init() { int fd; int err; /* create the door file for ipmgmtd */ if ((fd = open(IPMGMT_DOOR, O_CREAT|O_RDONLY, IPADM_FILE_MODE)) == -1) { err = errno; ipmgmt_log(LOG_ERR, "could not open %s: %s", IPMGMT_DOOR, strerror(err)); return (err); } (void) close(fd); if ((ipmgmt_door_fd = door_create(ipmgmt_handler, NULL, DOOR_REFUSE_DESC | DOOR_NO_CANCEL)) == -1) { err = errno; ipmgmt_log(LOG_ERR, "failed to create door: %s", strerror(err)); return (err); } /* * fdetach first in case a previous daemon instance exited * ungracefully. */ (void) fdetach(IPMGMT_DOOR); if (fattach(ipmgmt_door_fd, IPMGMT_DOOR) != 0) { err = errno; ipmgmt_log(LOG_ERR, "failed to attach door to %s: %s", IPMGMT_DOOR, strerror(err)); goto fail; } return (0); fail: (void) door_revoke(ipmgmt_door_fd); ipmgmt_door_fd = -1; return (err); } static void ipmgmt_door_fini() { if (ipmgmt_door_fd == -1) return; (void) fdetach(IPMGMT_DOOR); if (door_revoke(ipmgmt_door_fd) == -1) { ipmgmt_log(LOG_ERR, "failed to revoke access to door %s: %s", IPMGMT_DOOR, strerror(errno)); } } static int ipmgmt_init() { int err; if (signal(SIGTERM, ipmgmt_exit) == SIG_ERR || signal(SIGINT, ipmgmt_exit) == SIG_ERR) { err = errno; ipmgmt_log(LOG_ERR, "signal() for SIGTERM/INT failed: %s", strerror(err)); return (err); } if ((err = ipmgmt_db_init()) != 0 || (err = ipmgmt_door_init()) != 0) return (err); return (0); } /* * This is called by the child process to inform the parent process to * exit with the given return value. */ static void ipmgmt_inform_parent_exit(int rv) { if (write(pfds[1], &rv, sizeof (int)) != sizeof (int)) { ipmgmt_log(LOG_WARNING, "failed to inform parent process of status: %s", strerror(errno)); (void) close(pfds[1]); exit(EXIT_FAILURE); } (void) close(pfds[1]); } /*ARGSUSED*/ static void ipmgmt_exit(int signo) { (void) close(pfds[1]); ipmgmt_door_fini(); exit(EXIT_FAILURE); } /* * On the first reboot after installation of an ipkg zone, * ipmgmt_persist_if_cb() is used in non-global zones to track the interfaces * that have IP address configuration assignments from the global zone. * Persistent configuration for the interfaces is created on the first boot * by ipmgmtd, and the addresses assigned to the interfaces by the GZ * will be subsequently configured when the interface is enabled. * Note that ipmgmt_persist_if_cb() only sets up a list of interfaces * that need to be persisted- the actual update of the ipadm data-store happens * in ipmgmt_persist_if() after the appropriate privs/uid state has been set up. */ static void ipmgmt_persist_if_cb(char *ifname, boolean_t v4, boolean_t v6) { ipmgmt_pif_t *pif; pif = calloc(1, sizeof (*pif)); if (pif == NULL) { ipmgmt_log(LOG_WARNING, "Could not allocate memory to configure %s", ifname); return; } (void) strlcpy(pif->pif_ifname, ifname, sizeof (pif->pif_ifname)); pif->pif_v4 = v4; pif->pif_v6 = v6; pif->pif_next = ngz_pifs; ngz_pifs = pif; } /* * ipmgmt_ngz_init() initializes exclusive-IP stack non-global zones by * extracting configuration that has been saved in the kernel and applying * it at zone boot. */ static void ipmgmt_ngz_init() { zoneid_t zoneid; boolean_t firstboot = B_TRUE, s10c = B_FALSE; char brand[MAXNAMELEN]; ipadm_status_t ipstatus; zoneid = getzoneid(); if (zoneid != GLOBAL_ZONEID) { if (zone_getattr(zoneid, ZONE_ATTR_BRAND, brand, sizeof (brand)) < 0) { ipmgmt_log(LOG_ERR, "Could not get brand name"); return; } /* * firstboot is always true for S10C zones, where ipadm is not * available for restoring persistent configuration. */ if (strcmp(brand, NATIVE_BRAND_NAME) == 0) firstboot = ipmgmt_ngz_firstboot_postinstall(); else s10c = B_TRUE; if (!firstboot) return; ipstatus = ipadm_open(&iph, IPH_IPMGMTD); if (ipstatus != IPADM_SUCCESS) { ipmgmt_log(LOG_ERR, "could not open ipadm handle", ipadm_status2str(ipstatus)); return; } /* * Only pass down the callback to persist the interface * for NATIVE (ipkg) zones. */ (void) ipadm_init_net_from_gz(iph, NULL, (s10c ? NULL : ipmgmt_persist_if_cb)); ipadm_close(iph); } } /* * Set the uid of this daemon to the "netadm" user. Finish the following * operations before setuid() because they need root privileges: * * - create the /etc/svc/volatile/ipadm directory; * - change its uid/gid to "netadm"/"netadm"; */ static int ipmgmt_init_privileges() { struct stat statbuf; int err; /* create the IPADM_TMPFS_DIR directory */ if (stat(IPADM_TMPFS_DIR, &statbuf) < 0) { if (mkdir(IPADM_TMPFS_DIR, (mode_t)0755) < 0) { err = errno; goto fail; } } else { if ((statbuf.st_mode & S_IFMT) != S_IFDIR) { err = ENOTDIR; goto fail; } } if ((chmod(IPADM_TMPFS_DIR, 0755) < 0) || (chown(IPADM_TMPFS_DIR, UID_NETADM, GID_NETADM) < 0)) { err = errno; goto fail; } /* * initialize any NGZ specific network information before dropping * privileges. We need these privileges to plumb IP interfaces handed * down from the GZ (for dlpi_open() etc.) and also to configure the * address itself (for any IPI_PRIV ioctls like SLIFADDR) */ ipmgmt_ngz_init(); /* * Apply all protocol module properties. We need to apply all protocol * properties before we drop root privileges. */ ipmgmt_init_prop(); /* * limit the privileges of this daemon and set the uid of this * daemon to UID_NETADM */ if (__init_daemon_priv(PU_RESETGROUPS|PU_CLEARLIMITSET, UID_NETADM, GID_NETADM, NULL) == -1) { err = EPERM; goto fail; } return (0); fail: (void) ipmgmt_log(LOG_ERR, "failed to initialize the daemon: %s", strerror(err)); return (err); } /* * Keep the pfds fd open, close other fds. */ /*ARGSUSED*/ static int closefunc(void *arg, int fd) { if (fd != pfds[1]) (void) close(fd); return (0); } /* * We cannot use libc's daemon() because the door we create is associated with * the process ID. If we create the door before the call to daemon(), it will * be associated with the parent and it's incorrect. On the other hand if we * create the door later, after the call to daemon(), parent process exits * early and gives a false notion to SMF that 'ipmgmtd' is up and running, * which is incorrect. So, we have our own daemon() equivalent. */ static boolean_t ipmgmt_daemonize(void) { pid_t pid; int rv; if (pipe(pfds) < 0) { (void) fprintf(stderr, "%s: pipe() failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } if ((pid = fork()) == -1) { (void) fprintf(stderr, "%s: fork() failed: %s\n", progname, strerror(errno)); exit(EXIT_FAILURE); } else if (pid > 0) { /* Parent */ (void) close(pfds[1]); /* * Parent should not exit early, it should wait for the child * to return Success/Failure. If the parent exits early, then * SMF will think 'ipmgmtd' is up and would start all the * depended services. * * If the child process exits unexpectedly, read() returns -1. */ if (read(pfds[0], &rv, sizeof (int)) != sizeof (int)) { (void) kill(pid, SIGKILL); rv = EXIT_FAILURE; } (void) close(pfds[0]); exit(rv); } /* Child */ (void) close(pfds[0]); (void) setsid(); /* close all files except pfds[1] */ (void) fdwalk(closefunc, NULL); (void) chdir("/"); openlog(progname, LOG_PID, LOG_DAEMON); return (B_TRUE); } int main(int argc, char *argv[]) { int opt; boolean_t fg = B_FALSE; progname = strrchr(argv[0], '/'); if (progname != NULL) progname++; else progname = argv[0]; /* Process options */ while ((opt = getopt(argc, argv, "f")) != EOF) { switch (opt) { case 'f': fg = B_TRUE; break; default: (void) fprintf(stderr, "Usage: %s [-f]\n", progname); return (EXIT_FAILURE); } } if (!fg && getenv("SMF_FMRI") == NULL) { (void) fprintf(stderr, "ipmgmtd is a smf(7) managed service and cannot be run " "from the command line.\n"); return (EINVAL); } if (!fg && !ipmgmt_daemonize()) return (EXIT_FAILURE); if (ipmgmt_init_privileges() != 0) goto child_out; if (ipmgmt_init() != 0) goto child_out; /* Inform the parent process that it can successfully exit */ ipmgmt_inform_parent_exit(EXIT_SUCCESS); for (;;) (void) pause(); child_out: /* return from main() forcibly exits an MT process */ ipmgmt_inform_parent_exit(EXIT_FAILURE); return (EXIT_FAILURE); } /* * Persist any NGZ interfaces assigned to us from the global zone if they do * not already exist in the persistent db. We need to * do this before any calls to ipadm_enable_if() can succeed (i.e., * before opening up for door_calls), and after setuid to 'netadm' so that * the persistent db is created with the right permissions. */ static void ipmgmt_ngz_persist_if() { ipmgmt_pif_t *pif, *next; ipmgmt_if_arg_t ifarg; for (pif = ngz_pifs; pif != NULL; pif = next) { next = pif->pif_next; bzero(&ifarg, sizeof (ifarg)); (void) strlcpy(ifarg.ia_ifname, pif->pif_ifname, sizeof (ifarg.ia_ifname)); ifarg.ia_flags = IPMGMT_PERSIST; if (pif->pif_v4 && !ipmgmt_persist_if_exists(pif->pif_ifname, AF_INET)) { ifarg.ia_family = AF_INET; (void) ipmgmt_persist_if(&ifarg); } if (pif->pif_v6 && !ipmgmt_persist_if_exists(pif->pif_ifname, AF_INET6)) { ifarg.ia_family = AF_INET6; (void) ipmgmt_persist_if(&ifarg); } free(pif); } ngz_pifs = NULL; /* no red herrings */ } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright 2018 Joyent, Inc. * Copyright 2016 Argo Technologie SA. * Copyright (c) 2016-2017, Chris Fraire . * Copyright 2021 Tintri by DDN, Inc. All rights reserved. */ /* * Contains DB walker functions, which are of type `db_wfunc_t'; * * typedef boolean_t db_wfunc_t(void *cbarg, nvlist_t *db_nvl, char *buf, * size_t bufsize, int *errp); * * ipadm_rw_db() walks through the data store, one line at a time and calls * these call back functions with: * `cbarg' - callback argument * `db_nvl' - representing a line from DB in nvlist_t form * `buf' - character buffer to hold modified line * `bufsize'- size of the buffer * `errp' - captures any error inside the walker function. * * All the 'write' callback functions modify `db_nvl' based on `cbarg' and * copy string representation of `db_nvl' (using ipadm_nvlist2str()) into `buf'. * To delete a line from the DB, buf[0] is set to `\0'. Inside ipadm_rw_db(), * the modified `buf' is written back into DB. * * All the 'read' callback functions, retrieve the information from the DB, by * reading `db_nvl' and then populate the `cbarg'. */ #include #include #include #include #include #include #include #include #include #include "ipmgmt_impl.h" /* SCF related property group names and property names */ #define IPMGMTD_APP_PG "ipmgmtd" #define IPMGMTD_PROP_FBD "first_boot_done" #define IPMGMTD_PROP_DBVER "datastore_version" #define IPMGMTD_TRUESTR "true" #define ATYPE "_atype" /* name of the address type nvpair */ #define FLAGS "_flags" /* name of the flags nvpair */ /* * flag used by ipmgmt_persist_aobjmap() to indicate address type is * IPADM_ADDR_IPV6_ADDRCONF. */ #define IPMGMT_ATYPE_V6ACONF 0x1 extern pthread_rwlock_t ipmgmt_dbconf_lock; /* signifies whether volatile copy of data store is in use */ static boolean_t ipmgmt_rdonly_root = B_FALSE; typedef int ipmgmt_if_updater_func_t(nvlist_t *, nvpair_t *, uint_t); static ipmgmt_if_updater_func_t ipmgmt_if_family_updater; static ipmgmt_if_updater_func_t ipmgmt_if_groupmembers_updater; static int ipmgmt_get_ifinfo_nvl(const char *ifname, nvlist_t **if_info_nvl); typedef struct { const char *name; ipmgmt_if_updater_func_t *func; } ipmgmt_if_updater_ent_t; static ipmgmt_if_updater_ent_t ipmgmt_if_updater_ent[] = { {IPADM_NVP_FAMILIES, ipmgmt_if_family_updater}, {IPADM_NVP_MIFNAMES, ipmgmt_if_groupmembers_updater}, {NULL, NULL} }; static ipmgmt_if_updater_ent_t * ipmgmt_find_if_field_updater(const char *field_name) { int i; for (i = 0; ipmgmt_if_updater_ent[i].name != NULL; i++) { if (strcmp(field_name, ipmgmt_if_updater_ent[i].name) == 0) { break; } } return (&ipmgmt_if_updater_ent[i]); } static int ipmgmt_if_groupmembers_updater(nvlist_t *db_nvl, nvpair_t *member_nvp, uint_t flags) { char **members; char *member; char **out_members; uint_t nelem = 0, cnt = 0; int err; if ((err = nvpair_value_string(member_nvp, &member)) != 0) return (err); err = nvlist_lookup_string_array(db_nvl, IPADM_NVP_MIFNAMES, &members, &nelem); if (err != 0 && (flags & IPMGMT_REMOVE)) return (ENOENT); /* * Reserve one extra slot for IPMGMT_APPEND. * Probably not worth conditionalizing. */ out_members = calloc(nelem + 1, sizeof (char *)); if (out_members == NULL) return (ENOMEM); while (nelem-- > 0) { if ((flags & IPMGMT_REMOVE) && (strcmp(member, members[nelem]) == 0)) continue; if ((out_members[cnt] = strdup(members[nelem])) == NULL) { err = ENOMEM; goto fail; } cnt++; } if (flags & IPMGMT_APPEND) { if ((out_members[cnt] = strdup(member)) == NULL) { err = ENOMEM; goto fail; } cnt++; } if (cnt == 0) { err = nvlist_remove(db_nvl, IPADM_NVP_MIFNAMES, DATA_TYPE_STRING_ARRAY); } else { err = nvlist_add_string_array(db_nvl, IPADM_NVP_MIFNAMES, out_members, cnt); } fail: while (cnt--) free(out_members[cnt]); free(out_members); return (err); } static int ipmgmt_if_family_updater(nvlist_t *db_nvl, nvpair_t *families_nvp, uint_t flags) { uint16_t *families; uint_t nelem = 0; int err; if ((err = nvpair_value_uint16_array(families_nvp, &families, &nelem)) != 0) return (err); return (ipmgmt_update_family_nvp(db_nvl, families[0], flags)); } int ipmgmt_update_family_nvp(nvlist_t *nvl, sa_family_t af, uint_t flags) { uint16_t *families = NULL; uint16_t out_families[2]; uint_t nelem = 0, cnt; int err; err = nvlist_lookup_uint16_array(nvl, IPADM_NVP_FAMILIES, &families, &nelem); if (err != 0 && (flags & IPMGMT_REMOVE)) { return (ENOENT); } if (flags & IPMGMT_APPEND) { if (families != NULL) { if (nelem == 2 || families[0] == af) { return (EEXIST); } out_families[0] = families[0]; out_families[1] = af; cnt = 2; } else { out_families[0] = af; cnt = 1; } } else { assert(nelem == 1 || nelem == 2); cnt = 0; while (nelem-- > 0) { if (families[nelem] != af) { out_families[cnt] = families[nelem]; cnt++; } } } if (cnt != 0) { return (nvlist_add_uint16_array(nvl, IPADM_NVP_FAMILIES, out_families, cnt)); } return (nvlist_remove(nvl, IPADM_NVP_FAMILIES, DATA_TYPE_UINT16_ARRAY)); } /* * Checks if the database nvl, `db_nvl' contains and matches ALL of the passed * in private nvpairs `proto', `ifname' & `aobjname'. */ static boolean_t ipmgmt_nvlist_match(nvlist_t *db_nvl, const char *proto, const char *ifname, const char *aobjname) { char *db_proto = NULL, *db_ifname = NULL; char *db_aobjname = NULL; nvpair_t *nvp; char *name; /* walk through db_nvl and retrieve all its private nvpairs */ for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(db_nvl, nvp)) { name = nvpair_name(nvp); if (strcmp(IPADM_NVP_PROTONAME, name) == 0) (void) nvpair_value_string(nvp, &db_proto); else if (strcmp(IPADM_NVP_IFNAME, name) == 0) (void) nvpair_value_string(nvp, &db_ifname); else if (strcmp(IPADM_NVP_AOBJNAME, name) == 0) (void) nvpair_value_string(nvp, &db_aobjname); } if (proto != NULL && proto[0] == '\0') proto = NULL; if (ifname != NULL && ifname[0] == '\0') ifname = NULL; if (aobjname != NULL && aobjname[0] == '\0') aobjname = NULL; if ((proto == NULL && db_proto != NULL) || (proto != NULL && db_proto == NULL) || (proto != NULL && db_proto != NULL && strcmp(proto, db_proto) != 0)) { /* no intersection - different protocols. */ return (B_FALSE); } if ((ifname == NULL && db_ifname != NULL) || (ifname != NULL && db_ifname == NULL) || (ifname != NULL && db_ifname != NULL && strcmp(ifname, db_ifname) != 0)) { /* no intersection - different interfaces. */ return (B_FALSE); } if ((aobjname == NULL && db_aobjname != NULL) || (aobjname != NULL && db_aobjname == NULL) || (aobjname != NULL && db_aobjname != NULL && strcmp(aobjname, db_aobjname) != 0)) { /* no intersection - different address objects */ return (B_FALSE); } return (B_TRUE); } /* * Checks if the database nvl, `db_nvl' and the input nvl, `in_nvl' intersects. */ static boolean_t ipmgmt_nvlist_intersects(nvlist_t *db_nvl, nvlist_t *in_nvl) { nvpair_t *nvp; char *name; char *proto = NULL, *ifname = NULL, *aobjname = NULL; /* walk through in_nvl and retrieve all its private nvpairs */ for (nvp = nvlist_next_nvpair(in_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(in_nvl, nvp)) { name = nvpair_name(nvp); if (strcmp(IPADM_NVP_PROTONAME, name) == 0) (void) nvpair_value_string(nvp, &proto); else if (strcmp(IPADM_NVP_IFNAME, name) == 0) (void) nvpair_value_string(nvp, &ifname); else if (strcmp(IPADM_NVP_AOBJNAME, name) == 0) (void) nvpair_value_string(nvp, &aobjname); } return (ipmgmt_nvlist_match(db_nvl, proto, ifname, aobjname)); } /* * Checks if the database nvl, `db_nvl', contains and matches ANY of the passed * in private nvpairs `proto', `ifname' & `aobjname'. */ static boolean_t ipmgmt_nvlist_contains(nvlist_t *db_nvl, const char *proto, const char *ifname, char *aobjname) { char *db_ifname = NULL, *db_proto = NULL; char *db_aobjname = NULL; nvpair_t *nvp; char *name; /* walk through db_nvl and retrieve all private nvpairs */ for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(db_nvl, nvp)) { name = nvpair_name(nvp); if (strcmp(IPADM_NVP_PROTONAME, name) == 0) (void) nvpair_value_string(nvp, &db_proto); else if (strcmp(IPADM_NVP_IFNAME, name) == 0) (void) nvpair_value_string(nvp, &db_ifname); else if (strcmp(IPADM_NVP_AOBJNAME, name) == 0) (void) nvpair_value_string(nvp, &db_aobjname); } if (proto != NULL && proto[0] != '\0') { if ((db_proto == NULL || strcmp(proto, db_proto) != 0)) return (B_FALSE); } if (ifname != NULL && ifname[0] != '\0') { if ((db_ifname == NULL || strcmp(ifname, db_ifname) != 0)) return (B_FALSE); } if (aobjname != NULL && aobjname[0] != '\0') { if ((db_aobjname == NULL || strcmp(aobjname, db_aobjname) != 0)) return (B_FALSE); } return (B_TRUE); } /* * Retrieves the property value from the DB. The property whose value is to be * retrieved is in `pargp->ia_pname'. */ /* ARGSUSED */ boolean_t ipmgmt_db_getprop(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_prop_arg_t *pargp = arg; boolean_t cont = B_TRUE; char *pval; int err = 0; *errp = 0; if (!ipmgmt_nvlist_match(db_nvl, pargp->ia_module, pargp->ia_ifname, pargp->ia_aobjname)) return (B_TRUE); if ((err = nvlist_lookup_string(db_nvl, pargp->ia_pname, &pval)) == 0) { (void) strlcpy(pargp->ia_pval, pval, sizeof (pargp->ia_pval)); /* * We have retrieved what we are looking for. * Stop the walker. */ cont = B_FALSE; } else { if (err == ENOENT) err = 0; *errp = err; } return (cont); } /* * Removes the property value from the DB. The property whose value is to be * removed is in `pargp->ia_pname'. */ /* ARGSUSED */ boolean_t ipmgmt_db_resetprop(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_prop_arg_t *pargp = arg; *errp = 0; if (!ipmgmt_nvlist_match(db_nvl, pargp->ia_module, pargp->ia_ifname, pargp->ia_aobjname)) return (B_TRUE); if (!nvlist_exists(db_nvl, pargp->ia_pname)) return (B_TRUE); /* * We found the property in the DB. If IPMGMT_REMOVE is not set then * delete the entry from the db. If it is set, then the property is a * multi-valued property so just remove the specified values from DB. */ if (pargp->ia_flags & IPMGMT_REMOVE) { char *dbpval = NULL; char *inpval = pargp->ia_pval; char pval[MAXPROPVALLEN]; char *val, *lasts; *errp = nvlist_lookup_string(db_nvl, pargp->ia_pname, &dbpval); if (*errp != 0) return (B_FALSE); /* * multi-valued properties are represented as comma separated * values. Use string tokenizer functions to split them and * search for the value to be removed. */ bzero(pval, sizeof (pval)); if ((val = strtok_r(dbpval, ",", &lasts)) != NULL) { if (strcmp(val, inpval) != 0) (void) strlcat(pval, val, MAXPROPVALLEN); while ((val = strtok_r(NULL, ",", &lasts)) != NULL) { if (strcmp(val, inpval) != 0) { if (pval[0] != '\0') (void) strlcat(pval, ",", MAXPROPVALLEN); (void) strlcat(pval, val, MAXPROPVALLEN); } } } else { if (strcmp(dbpval, inpval) != 0) *errp = ENOENT; else buf[0] = '\0'; return (B_FALSE); } *errp = nvlist_add_string(db_nvl, pargp->ia_pname, pval); if (*errp != 0) return (B_FALSE); (void) memset(buf, 0, buflen); if (ipadm_nvlist2str(db_nvl, buf, buflen) == 0) { /* buffer overflow */ *errp = ENOBUFS; } } else { buf[0] = '\0'; } /* stop the search */ return (B_FALSE); } /* * Input arguments can have IPADM_NVP_AOBJNAME or IPADM_NVP_IFNAME. A match is * found, when one of the following occurs first. * - the input aobjname matches the db aobjname. Return the db address. * - the input interface matches the db interface. Return all the * matching db lines with addresses. */ /* ARGSUSED */ boolean_t ipmgmt_db_getaddr(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_get_cbarg_t *cbarg = arg; char *db_aobjname = NULL; char *db_ifname = NULL; nvlist_t *db_addr = NULL; char name[IPMGMT_STRSIZE]; nvpair_t *nvp; boolean_t add_nvl = B_FALSE; /* Parse db nvlist */ for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(db_nvl, nvp)) { if (nvpair_type(nvp) == DATA_TYPE_NVLIST) (void) nvpair_value_nvlist(nvp, &db_addr); else if (strcmp(nvpair_name(nvp), IPADM_NVP_IFNAME) == 0) (void) nvpair_value_string(nvp, &db_ifname); else if (strcmp(nvpair_name(nvp), IPADM_NVP_AOBJNAME) == 0) (void) nvpair_value_string(nvp, &db_aobjname); } if (db_aobjname == NULL) /* Not an address */ return (B_TRUE); /* Check for a match between the aobjnames or the interface name */ if (cbarg->cb_aobjname[0] != '\0') { if (strcmp(cbarg->cb_aobjname, db_aobjname) == 0) add_nvl = B_TRUE; } else if (cbarg->cb_ifname[0] != '\0') { if (strcmp(cbarg->cb_ifname, db_ifname) == 0) add_nvl = B_TRUE; } else { add_nvl = B_TRUE; } if (add_nvl) { (void) snprintf(name, sizeof (name), "%s_%d", db_ifname, cbarg->cb_ocnt); *errp = nvlist_add_nvlist(cbarg->cb_onvl, name, db_nvl); if (*errp == 0) cbarg->cb_ocnt++; } return (B_TRUE); } /* * This function only gets called if a volatile filesystem version * of the configuration file has been created. This only happens in the * extremely rare case that a request has been made to update the configuration * file at boottime while the root filesystem was read-only. This is * really a rare occurrence now that we don't support UFS root filesystems * any longer. This function will periodically attempt to write the * configuration back to its location on the root filesystem. Success * will indicate that the filesystem is no longer read-only. */ /* ARGSUSED */ static void * ipmgmt_db_restore_thread(void *arg) { int err; for (;;) { (void) sleep(5); (void) pthread_rwlock_wrlock(&ipmgmt_dbconf_lock); if (!ipmgmt_rdonly_root) break; err = ipmgmt_cpfile(IPADM_VOL_DB_FILE, IPADM_DB_FILE, B_FALSE); if (err == 0) { ipmgmt_rdonly_root = B_FALSE; break; } (void) pthread_rwlock_unlock(&ipmgmt_dbconf_lock); } (void) pthread_rwlock_unlock(&ipmgmt_dbconf_lock); return (NULL); } /* * This function takes the appropriate lock, read or write, based on the * `db_op' and then calls DB walker ipadm_rw_db(). The code is complicated * by the fact that we are not always guaranteed to have a writable root * filesystem since it is possible that we are reading or writing during * bootime while the root filesystem is still read-only. This is, by far, * the exception case. Normally, this function will be called when the * root filesystem is writable. In the unusual case where this is not * true, the configuration file is copied to the volatile file system * and is updated there until the root filesystem becomes writable. At * that time the file will be moved back to its proper location by * ipmgmt_db_restore_thread(). */ extern int ipmgmt_db_walk(db_wfunc_t *db_walk_func, void *db_warg, ipadm_db_op_t db_op) { int err; boolean_t writeop; mode_t mode; pthread_t tid; pthread_attr_t attr; writeop = (db_op != IPADM_DB_READ); if (writeop) { (void) pthread_rwlock_wrlock(&ipmgmt_dbconf_lock); mode = IPADM_FILE_MODE; } else { (void) pthread_rwlock_rdlock(&ipmgmt_dbconf_lock); mode = 0; } /* * Did a previous write attempt fail? If so, don't even try to * read/write to IPADM_DB_FILE. */ if (!ipmgmt_rdonly_root) { err = ipadm_rw_db(db_walk_func, db_warg, IPADM_DB_FILE, mode, db_op); if (err != EROFS) goto done; } /* * If we haven't already copied the file to the volatile * file system, do so. This should only happen on a failed * writeop(i.e., we have acquired the write lock above). */ if (access(IPADM_VOL_DB_FILE, F_OK) != 0) { assert(writeop); err = ipmgmt_cpfile(IPADM_DB_FILE, IPADM_VOL_DB_FILE, B_TRUE); if (err != 0) goto done; (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); (void) pthread_attr_setname_np(&attr, "db_restore"); err = pthread_create(&tid, &attr, ipmgmt_db_restore_thread, NULL); (void) pthread_attr_destroy(&attr); if (err != 0) { (void) unlink(IPADM_VOL_DB_FILE); goto done; } ipmgmt_rdonly_root = B_TRUE; } /* * Read/write from the volatile copy. */ err = ipadm_rw_db(db_walk_func, db_warg, IPADM_VOL_DB_FILE, mode, db_op); done: (void) pthread_rwlock_unlock(&ipmgmt_dbconf_lock); return (err); } /* * Used to add an entry towards the end of DB. It just returns B_TRUE for * every line of the DB. When we reach the end, ipadm_rw_db() adds the * line at the end. */ /* ARGSUSED */ boolean_t ipmgmt_db_add(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { return (B_TRUE); } /* * This function is used to update or create an entry in DB. The nvlist_t, * `in_nvl', represents the line we are looking for. Once we ensure the right * line from DB, we update that entry. */ boolean_t ipmgmt_db_update(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipadm_dbwrite_cbarg_t *cb = arg; uint_t flags = cb->dbw_flags; nvlist_t *in_nvl = cb->dbw_nvl; nvpair_t *nvp; char *name, *instrval = NULL, *dbstrval = NULL; char pval[MAXPROPVALLEN]; *errp = 0; if (!ipmgmt_nvlist_intersects(db_nvl, in_nvl)) return (B_TRUE); for (nvp = nvlist_next_nvpair(in_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(in_nvl, nvp)) { name = nvpair_name(nvp); if (!IPADM_PRIV_NVP(name) && nvlist_exists(db_nvl, name)) break; } if (nvp == NULL) return (B_TRUE); assert(nvpair_type(nvp) == DATA_TYPE_STRING); if ((*errp = nvpair_value_string(nvp, &instrval)) != 0) return (B_FALSE); /* * If IPMGMT_APPEND is set then we are dealing with multi-valued * properties. We append to the entry from the db, with the new value. */ if (flags & IPMGMT_APPEND) { if ((*errp = nvlist_lookup_string(db_nvl, name, &dbstrval)) != 0) return (B_FALSE); (void) snprintf(pval, MAXPROPVALLEN, "%s,%s", dbstrval, instrval); if ((*errp = nvlist_add_string(db_nvl, name, pval)) != 0) return (B_FALSE); } else { /* case of in-line update of a db entry */ if ((*errp = nvlist_add_string(db_nvl, name, instrval)) != 0) return (B_FALSE); } (void) memset(buf, 0, buflen); if (ipadm_nvlist2str(db_nvl, buf, buflen) == 0) { /* buffer overflow */ *errp = ENOBUFS; } /* we updated the DB entry, so do not continue */ return (B_FALSE); } /* * This function is used to update a DB line that describes * an interface, its family and group interface * */ boolean_t ipmgmt_db_update_if(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipadm_dbwrite_cbarg_t *cb = arg; ipmgmt_if_updater_ent_t *updater; nvlist_t *in_nvl = cb->dbw_nvl; uint_t flags = cb->dbw_flags; nvpair_t *nvp; char *name; char *db_ifname; char *gifname = NULL; char *mifname = NULL; *errp = 0; /* Only one flag */ if ((flags & (IPMGMT_APPEND | IPMGMT_REMOVE)) == 0 || ((flags & IPMGMT_APPEND) && (flags & IPMGMT_REMOVE))) { *errp = EINVAL; return (B_FALSE); } if (!nvlist_exists(db_nvl, IPADM_NVP_FAMILIES)) return (B_TRUE); if (nvlist_exists(db_nvl, IPADM_NVP_IFCLASS) && nvlist_lookup_string(db_nvl, IPADM_NVP_IFNAME, &db_ifname) == 0 && nvlist_lookup_string(in_nvl, IPADM_NVP_GIFNAME, &gifname) == 0 && nvlist_lookup_string(in_nvl, IPADM_NVP_MIFNAMES, &mifname) == 0 && strcmp(db_ifname, mifname) == 0) { if (flags & IPMGMT_APPEND) { if ((*errp = nvlist_add_string(db_nvl, IPADM_NVP_GIFNAME, gifname)) != 0) return (B_FALSE); } else { if ((*errp = nvlist_remove(db_nvl, IPADM_NVP_GIFNAME, DATA_TYPE_STRING)) != 0) return (B_FALSE); } cb->dbw_flags &= ~IPMGMT_UPDATE_IPMP; goto done; } if (!ipmgmt_nvlist_intersects(db_nvl, in_nvl)) return (B_TRUE); for (nvp = nvlist_next_nvpair(in_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(in_nvl, nvp)) { name = nvpair_name(nvp); if (strcmp(name, IPADM_NVP_FAMILIES) != 0 && strcmp(name, IPADM_NVP_MIFNAMES) != 0) continue; updater = ipmgmt_find_if_field_updater(name); assert(updater != NULL); *errp = (*updater->func)(db_nvl, nvp, flags); if (*errp != 0) return (B_FALSE); } cb->dbw_flags &= ~IPMGMT_UPDATE_IF; done: (void) memset(buf, 0, buflen); if (ipadm_nvlist2str(db_nvl, buf, buflen) == 0) { *errp = EOVERFLOW; return (B_FALSE); } /* we finished all operations, so do not continue */ if ((cb->dbw_flags & (IPMGMT_UPDATE_IF | IPMGMT_UPDATE_IPMP)) == 0) return (B_FALSE); return (B_TRUE); } /* * For the given `cbarg->cb_ifname' interface, retrieve the nvlist that * represents the persistent interface information. * The nvlist contains: * IPADM_NVP_IFNAME * IPADM_NVP_FAMILIES * IPADM_NVP_IF_CLASS * * (used in 'ipadm show-if') */ /* ARGSUSED */ boolean_t ipmgmt_db_getif(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_get_cbarg_t *cbarg = arg; char *ifname = cbarg->cb_ifname; nvpair_t *nvp; char *db_ifname = NULL; boolean_t families = B_FALSE; /* Parse db nvlist */ for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(db_nvl, nvp)) { if (strcmp(nvpair_name(nvp), IPADM_NVP_IFNAME) == 0) { (void) nvpair_value_string(nvp, &db_ifname); } else if (strcmp(nvpair_name(nvp), IPADM_NVP_FAMILIES) == 0) { families = B_TRUE; } } if (db_ifname == NULL || !families) return (B_TRUE); if (ifname != NULL && ifname[0] != '\0' && strcmp(ifname, db_ifname) != 0) return (B_TRUE); *errp = nvlist_add_nvlist(cbarg->cb_onvl, db_ifname, db_nvl); if (*errp == 0) cbarg->cb_ocnt++; if (ifname != NULL && ifname[0] != '\0') return (B_FALSE); return (B_TRUE); } /* * Deletes those entries from the database for which interface name * matches with the given `cbarg->cb_ifname' */ /* ARGSUSED */ boolean_t ipmgmt_db_resetif(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_if_cbarg_t *cbarg = arg; boolean_t isv6 = (cbarg->cb_family == AF_INET6); char *ifname = cbarg->cb_ifname; char *modstr = NULL; char *aobjname; uint_t proto; ipmgmt_aobjmap_t *head; boolean_t aobjfound = B_FALSE; *errp = 0; if (!ipmgmt_nvlist_contains(db_nvl, NULL, ifname, NULL)) return (B_TRUE); if (nvlist_exists(db_nvl, IPADM_NVP_FAMILIES)) { if ((*errp = ipmgmt_update_family_nvp(db_nvl, cbarg->cb_family, IPMGMT_REMOVE)) != 0) { return (B_FALSE); } if (cbarg->cb_family == AF_INET) { cbarg->cb_ipv4exists = B_FALSE; } else { assert(cbarg->cb_family == AF_INET6); cbarg->cb_ipv6exists = B_FALSE; } if (!nvlist_exists(db_nvl, IPADM_NVP_FAMILIES)) { cbarg->cb_ipv4exists = B_FALSE; cbarg->cb_ipv6exists = B_FALSE; goto delete; } /* Otherwise need to reconstruct this string */ (void) memset(buf, 0, buflen); if (ipadm_nvlist2str(db_nvl, buf, buflen) == 0) { /* buffer overflow */ *errp = EOVERFLOW; return (B_FALSE); } return (B_TRUE); } /* Reset all the interface configurations for 'ifname' */ if (isv6 && (nvlist_exists(db_nvl, IPADM_NVP_IPV6ADDR) || nvlist_exists(db_nvl, IPADM_NVP_INTFID))) { goto delete; } if (!isv6 && (nvlist_exists(db_nvl, IPADM_NVP_IPV4ADDR) || nvlist_exists(db_nvl, IPADM_NVP_DHCP))) { goto delete; } if (nvlist_lookup_string(db_nvl, IPADM_NVP_AOBJNAME, &aobjname) == 0) { /* * This must be an address property. Delete this * line if there is a match in the address family. */ head = aobjmap.aobjmap_head; while (head != NULL) { if (strcmp(head->am_aobjname, aobjname) == 0) { aobjfound = B_TRUE; if (head->am_family == cbarg->cb_family) goto delete; } head = head->am_next; } /* * If aobjfound = B_FALSE, then this address is not * available in active configuration. We should go ahead * and delete it. */ if (!aobjfound) goto delete; } /* * If we are removing both v4 and v6 interface, then we get rid of * all the properties for that interface. On the other hand, if we * are deleting only v4 instance of an interface, then we delete v4 * properties only. */ if (nvlist_lookup_string(db_nvl, IPADM_NVP_PROTONAME, &modstr) == 0) { proto = ipadm_str2proto(modstr); switch (proto) { case MOD_PROTO_IPV6: if (isv6) goto delete; break; case MOD_PROTO_IPV4: if (!isv6) goto delete; break; case MOD_PROTO_IP: if (!cbarg->cb_ipv4exists && !cbarg->cb_ipv6exists) goto delete; break; } } /* Not found a match yet. Continue processing the db */ return (B_TRUE); delete: /* delete the line from the db */ buf[0] = '\0'; return (B_TRUE); } /* * Deletes those entries from the database for which address object name * matches with the given `cbarg->cb_aobjname' */ /* ARGSUSED */ boolean_t ipmgmt_db_resetaddr(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_resetaddr_cbarg_t *cbarg = arg; char *aobjname = cbarg->cb_aobjname; *errp = 0; if (!ipmgmt_nvlist_contains(db_nvl, NULL, NULL, aobjname)) return (B_TRUE); /* delete the line from the db */ buf[0] = '\0'; return (B_TRUE); } /* * Retrieves all interface props, including addresses, for given interface(s). * `invl' contains the list of interfaces, for which information need to be * retrieved. */ /* ARGSUSED */ boolean_t ipmgmt_db_initif(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_initif_cbarg_t *cbarg = arg; nvlist_t *onvl = cbarg->cb_onvl; nvlist_t *invl = cbarg->cb_invl; sa_family_t in_af = cbarg->cb_family; char *db_ifname; *errp = 0; if (nvlist_lookup_string(db_nvl, IPADM_NVP_IFNAME, &db_ifname) == 0 && nvlist_exists(invl, db_ifname)) { char name[IPMGMT_STRSIZE]; sa_family_t db_af = in_af; uint_t proto; char *pstr; if (in_af != AF_UNSPEC) { if (nvlist_lookup_string(db_nvl, IPADM_NVP_PROTONAME, &pstr) == 0) { proto = ipadm_str2proto(pstr); if (proto == MOD_PROTO_IPV4) db_af = AF_INET; else if (proto == MOD_PROTO_IPV6) db_af = AF_INET6; else db_af = in_af; } else { if (nvlist_exists(db_nvl, IPADM_NVP_IPV4ADDR) || nvlist_exists(db_nvl, IPADM_NVP_DHCP)) db_af = AF_INET; else db_af = AF_INET6; } } if (in_af == db_af) { (void) snprintf(name, sizeof (name), "%s_%d", db_ifname, cbarg->cb_ocnt); *errp = nvlist_add_nvlist(onvl, name, db_nvl); if (*errp == 0) cbarg->cb_ocnt++; } } return (B_TRUE); } /* * helper function for ipmgmt_aobjmap_op(). Adds the node pointed by `nodep' * into `aobjmap' structure. */ static int i_ipmgmt_add_amnode(ipmgmt_aobjmap_t *nodep) { ipmgmt_aobjmap_t *new, *head; head = aobjmap.aobjmap_head; if ((new = malloc(sizeof (ipmgmt_aobjmap_t))) == NULL) return (ENOMEM); *new = *nodep; new->am_next = NULL; /* Add the node at the beginning of the list */ if (head == NULL) { aobjmap.aobjmap_head = new; } else { new->am_next = aobjmap.aobjmap_head; aobjmap.aobjmap_head = new; } return (0); } /* * A recursive function to generate alphabetized number given a decimal number. * Decimal 0 to 25 maps to 'a' to 'z' and then the counting continues with 'aa', * 'ab', 'ac', et al. */ static void i_ipmgmt_num2priv_aobjname(uint32_t num, char **cp, char *endp) { if (num >= 26) i_ipmgmt_num2priv_aobjname(num / 26 - 1, cp, endp); if (*cp != endp) { *cp[0] = 'a' + (num % 26); (*cp)++; } } /* * This function generates an `aobjname', when required, and then does * lookup-add. If `nodep->am_aobjname' is not an empty string, then it walks * through the `aobjmap' to check if an address object with the same * `nodep->am_aobjname' exists. If it exists, EEXIST is returned as duplicate * `aobjname's are not allowed. * * If `nodep->am_aobjname' is an empty string then the daemon generates an * `aobjname' using the `am_nextnum', which contains the next number to be * used to generate `aobjname'. `am_nextnum' is converted to base26 using * `a-z' alphabets in i_ipmgmt_num2priv_aobjname(). * * `am_nextnum' will be 0 to begin with. Every time an address object that * needs `aobjname' is added it's incremented by 1. So for the first address * object on net0 the `am_aobjname' will be net0/_a and `am_nextnum' will be 1. * For the second address object on that interface `am_aobjname' will be net0/_b * and `am_nextnum' will incremented to 2. */ static int i_ipmgmt_lookupadd_amnode(ipmgmt_aobjmap_t *nodep) { ipmgmt_aobjmap_t *head; uint32_t nextnum; for (head = aobjmap.aobjmap_head; head != NULL; head = head->am_next) if (strcmp(head->am_ifname, nodep->am_ifname) == 0) break; nextnum = (head == NULL ? 0 : head->am_nextnum); /* * if `aobjname' is empty, then the daemon has to generate the * next `aobjname' for the given interface and family. */ if (nodep->am_aobjname[0] == '\0') { char tmpstr[IPADM_AOBJ_USTRSIZ - 1]; /* 1 for leading '_' */ char *cp = tmpstr; char *endp = tmpstr + sizeof (tmpstr); i_ipmgmt_num2priv_aobjname(nextnum, &cp, endp); if (cp == endp) return (EINVAL); cp[0] = '\0'; if (snprintf(nodep->am_aobjname, IPADM_AOBJSIZ, "%s/_%s", nodep->am_ifname, tmpstr) >= IPADM_AOBJSIZ) { return (EINVAL); } nodep->am_nextnum = ++nextnum; } else { for (head = aobjmap.aobjmap_head; head != NULL; head = head->am_next) { if (strcmp(head->am_aobjname, nodep->am_aobjname) == 0) return (EEXIST); } nodep->am_nextnum = nextnum; } return (i_ipmgmt_add_amnode(nodep)); } /* * Performs following operations on the global `aobjmap' linked list. * (a) ADDROBJ_ADD: add or update address object in `aobjmap' * (b) ADDROBJ_DELETE: delete address object from `aobjmap' * (c) ADDROBJ_LOOKUPADD: place a stub address object in `aobjmap' * (d) ADDROBJ_SETLIFNUM: Sets the lifnum for an address object in `aobjmap' */ int ipmgmt_aobjmap_op(ipmgmt_aobjmap_t *nodep, uint32_t op) { ipmgmt_aobjmap_t *head, *prev, *matched = NULL; boolean_t update = B_TRUE; int err = 0; ipadm_db_op_t db_op = IPADM_DB_READ; (void) pthread_rwlock_wrlock(&aobjmap.aobjmap_rwlock); head = aobjmap.aobjmap_head; switch (op) { case ADDROBJ_ADD: /* * check for stub nodes (added by ADDROBJ_LOOKUPADD) and * update, else add the new node. */ for (; head != NULL; head = head->am_next) { /* * For IPv6, we need to distinguish between the * linklocal and non-linklocal nodes */ if (strcmp(head->am_aobjname, nodep->am_aobjname) == 0 && (head->am_atype != IPADM_ADDR_IPV6_ADDRCONF || head->ipmgmt_am_linklocal == nodep->ipmgmt_am_linklocal)) break; } if (head != NULL) { /* update the node */ (void) strlcpy(head->am_ifname, nodep->am_ifname, sizeof (head->am_ifname)); head->am_lnum = nodep->am_lnum; head->am_family = nodep->am_family; head->am_flags = nodep->am_flags; head->am_atype = nodep->am_atype; head->am_atype_cache = nodep->am_atype_cache; } else { for (head = aobjmap.aobjmap_head; head != NULL; head = head->am_next) { if (strcmp(head->am_ifname, nodep->am_ifname) == 0) break; } nodep->am_nextnum = (head == NULL ? 0 : head->am_nextnum); err = i_ipmgmt_add_amnode(nodep); } db_op = IPADM_DB_WRITE; break; case ADDROBJ_DELETE: prev = head; while (head != NULL) { if (strcmp(head->am_aobjname, nodep->am_aobjname) == 0) { nodep->am_atype = head->am_atype; /* * There could be multiple IPV6_ADDRCONF nodes, * with same address object name, so check for * logical number also. */ if (head->am_atype != IPADM_ADDR_IPV6_ADDRCONF || nodep->am_lnum == head->am_lnum) break; } prev = head; head = head->am_next; } if (head != NULL) { /* * If the address object is in both active and * persistent configuration and the user is deleting it * only from active configuration then mark this node * for deletion by reseting IPMGMT_ACTIVE bit. * With this the same address object name cannot * be reused until it is permanently removed. */ if (head->am_flags == (IPMGMT_ACTIVE|IPMGMT_PERSIST) && nodep->am_flags == IPMGMT_ACTIVE) { /* Update flags in the in-memory map. */ head->am_flags &= ~IPMGMT_ACTIVE; head->am_lnum = -1; /* Update info in file. */ db_op = IPADM_DB_WRITE; *nodep = *head; } else { (void) strlcpy(nodep->am_ifname, head->am_ifname, sizeof (nodep->am_ifname)); /* otherwise delete the node */ if (head == aobjmap.aobjmap_head) aobjmap.aobjmap_head = head->am_next; else prev->am_next = head->am_next; free(head); db_op = IPADM_DB_DELETE; } } else { err = ENOENT; } break; case ADDROBJ_LOOKUPADD: err = i_ipmgmt_lookupadd_amnode(nodep); update = B_FALSE; break; case ADDROBJ_SETLIFNUM: update = B_FALSE; for (; head != NULL; head = head->am_next) { if (strcmp(head->am_ifname, nodep->am_ifname) == 0 && head->am_family == nodep->am_family && head->am_lnum == nodep->am_lnum) { err = EEXIST; break; } if (strcmp(head->am_aobjname, nodep->am_aobjname) == 0) { matched = head; } } if (err == EEXIST) break; if (matched != NULL) { /* update the lifnum */ matched->am_lnum = nodep->am_lnum; } else { err = ENOENT; } break; default: assert(0); } if (err == 0 && update) err = ipmgmt_persist_aobjmap(nodep, db_op); (void) pthread_rwlock_unlock(&aobjmap.aobjmap_rwlock); return (err); } /* * Given a node in `aobjmap', this function converts it into nvlist_t structure. * The content to be written to DB must be represented as nvlist_t. */ static int i_ipmgmt_node2nvl(nvlist_t **nvl, ipmgmt_aobjmap_t *np) { int err; char strval[IPMGMT_STRSIZE]; *nvl = NULL; if ((err = nvlist_alloc(nvl, NV_UNIQUE_NAME, 0)) != 0) goto fail; if ((err = nvlist_add_string(*nvl, IPADM_NVP_AOBJNAME, np->am_aobjname)) != 0) goto fail; if ((err = nvlist_add_string(*nvl, IPADM_NVP_IFNAME, np->am_ifname)) != 0) goto fail; (void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_lnum); if ((err = nvlist_add_string(*nvl, IPADM_NVP_LIFNUM, strval)) != 0) goto fail; (void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_family); if ((err = nvlist_add_string(*nvl, IPADM_NVP_FAMILY, strval)) != 0) goto fail; (void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_flags); if ((err = nvlist_add_string(*nvl, FLAGS, strval)) != 0) goto fail; (void) snprintf(strval, IPMGMT_STRSIZE, "%d", np->am_atype); if ((err = nvlist_add_string(*nvl, ATYPE, strval)) != 0) goto fail; switch (np->am_atype) { case IPADM_ADDR_IPV6_ADDRCONF: { struct sockaddr_in6 *in6; in6 = &np->ipmgmt_am_ifid; if (np->ipmgmt_am_linklocal && IN6_IS_ADDR_UNSPECIFIED(&in6->sin6_addr)) { if ((err = nvlist_add_string(*nvl, IPADM_NVP_IPNUMADDR, "default")) != 0) { goto fail; } } else { if (inet_ntop(AF_INET6, &in6->sin6_addr, strval, IPMGMT_STRSIZE) == NULL) { err = errno; goto fail; } if ((err = nvlist_add_string(*nvl, IPADM_NVP_IPNUMADDR, strval)) != 0) { goto fail; } } } break; case IPADM_ADDR_DHCP: { if (*np->ipmgmt_am_reqhost != '\0' && (err = nvlist_add_string(*nvl, IPADM_NVP_REQHOST, np->ipmgmt_am_reqhost)) != 0) goto fail; } /* FALLTHRU */ default: if ((err = nvlist_add_string(*nvl, IPADM_NVP_IPNUMADDR, "")) != 0) goto fail; break; } return (err); fail: nvlist_free(*nvl); return (err); } /* * Read the aobjmap data store and build the in-memory representation * of the aobjmap. We don't need to hold any locks while building this as * we do this in very early stage of daemon coming up, even before the door * is opened. */ /* ARGSUSED */ extern boolean_t ipmgmt_aobjmap_init(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { nvpair_t *nvp = NULL; char *name, *strval = NULL; ipmgmt_aobjmap_t node; struct sockaddr_in6 *in6; *errp = 0; node.am_next = NULL; for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(db_nvl, nvp)) { name = nvpair_name(nvp); if ((*errp = nvpair_value_string(nvp, &strval)) != 0) return (B_TRUE); if (strcmp(IPADM_NVP_AOBJNAME, name) == 0) { (void) strlcpy(node.am_aobjname, strval, sizeof (node.am_aobjname)); } else if (strcmp(IPADM_NVP_IFNAME, name) == 0) { (void) strlcpy(node.am_ifname, strval, sizeof (node.am_ifname)); } else if (strcmp(IPADM_NVP_LIFNUM, name) == 0) { node.am_lnum = atoi(strval); } else if (strcmp(IPADM_NVP_FAMILY, name) == 0) { node.am_family = (sa_family_t)atoi(strval); } else if (strcmp(FLAGS, name) == 0) { node.am_flags = atoi(strval); } else if (strcmp(ATYPE, name) == 0) { node.am_atype = (ipadm_addr_type_t)atoi(strval); } else if (strcmp(IPADM_NVP_IPNUMADDR, name) == 0) { if (node.am_atype == IPADM_ADDR_IPV6_ADDRCONF) { in6 = &node.ipmgmt_am_ifid; if (strcmp(strval, "default") == 0) { bzero(in6, sizeof (node.ipmgmt_am_ifid)); node.ipmgmt_am_linklocal = B_TRUE; } else { (void) inet_pton(AF_INET6, strval, &in6->sin6_addr); if (IN6_IS_ADDR_UNSPECIFIED( &in6->sin6_addr)) node.ipmgmt_am_linklocal = B_TRUE; } } } } /* we have all the information we need, add the node */ *errp = i_ipmgmt_add_amnode(&node); return (B_TRUE); } /* * Updates an entry from the temporary cache file, which matches the given * address object name. */ /* ARGSUSED */ static boolean_t ipmgmt_update_aobjmap(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipadm_dbwrite_cbarg_t *cb = arg; nvlist_t *in_nvl = cb->dbw_nvl; uint32_t flags = cb->dbw_flags; char *db_lifnumstr = NULL, *in_lifnumstr = NULL; *errp = 0; if (!ipmgmt_nvlist_intersects(db_nvl, in_nvl)) return (B_TRUE); if (flags & IPMGMT_ATYPE_V6ACONF) { if (nvlist_lookup_string(db_nvl, IPADM_NVP_LIFNUM, &db_lifnumstr) != 0 || nvlist_lookup_string(in_nvl, IPADM_NVP_LIFNUM, &in_lifnumstr) != 0 || (atoi(db_lifnumstr) != -1 && atoi(in_lifnumstr) != -1 && strcmp(db_lifnumstr, in_lifnumstr) != 0)) return (B_TRUE); } /* we found the match */ (void) memset(buf, 0, buflen); if (ipadm_nvlist2str(in_nvl, buf, buflen) == 0) { /* buffer overflow */ *errp = ENOBUFS; } /* stop the walker */ return (B_FALSE); } /* * Deletes an entry from the temporary cache file, which matches the given * address object name. */ /* ARGSUSED */ static boolean_t ipmgmt_delete_aobjmap(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipmgmt_aobjmap_t *nodep = arg; char *db_lifnumstr = NULL; *errp = 0; if (!ipmgmt_nvlist_match(db_nvl, NULL, nodep->am_ifname, nodep->am_aobjname)) return (B_TRUE); if (nodep->am_atype == IPADM_ADDR_IPV6_ADDRCONF) { if (nvlist_lookup_string(db_nvl, IPADM_NVP_LIFNUM, &db_lifnumstr) != 0 || atoi(db_lifnumstr) != nodep->am_lnum) return (B_TRUE); } /* we found the match, delete the line from the db */ buf[0] = '\0'; /* stop the walker */ return (B_FALSE); } /* * Adds or deletes aobjmap node information into a temporary cache file. */ extern int ipmgmt_persist_aobjmap(ipmgmt_aobjmap_t *nodep, ipadm_db_op_t op) { int err; ipadm_dbwrite_cbarg_t cb; nvlist_t *nvl = NULL; if (op == IPADM_DB_WRITE) { if ((err = i_ipmgmt_node2nvl(&nvl, nodep)) != 0) return (err); cb.dbw_nvl = nvl; if (nodep->am_atype == IPADM_ADDR_IPV6_ADDRCONF) cb.dbw_flags = IPMGMT_ATYPE_V6ACONF; else cb.dbw_flags = 0; err = ipadm_rw_db(ipmgmt_update_aobjmap, &cb, ADDROBJ_MAPPING_DB_FILE, IPADM_FILE_MODE, IPADM_DB_WRITE); nvlist_free(nvl); } else { assert(op == IPADM_DB_DELETE); err = ipadm_rw_db(ipmgmt_delete_aobjmap, nodep, ADDROBJ_MAPPING_DB_FILE, IPADM_FILE_MODE, IPADM_DB_DELETE); } return (err); } /* * upgrades the ipadm data-store. It renames all the old private protocol * property names which start with leading protocol names to begin with * IPADM_PRIV_PROP_PREFIX. */ /* ARGSUSED */ boolean_t ipmgmt_db_upgrade(void *arg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { nvpair_t *nvp; char *name, *pname = NULL, *protostr = NULL, *pval = NULL; uint_t proto, nproto; char nname[IPMGMT_STRSIZE], tmpstr[IPMGMT_STRSIZE]; *errp = 0; /* * We are interested in lines which contain protocol properties. We * walk through other lines in the DB. */ if (nvlist_exists(db_nvl, IPADM_NVP_IFNAME) || nvlist_exists(db_nvl, IPADM_NVP_AOBJNAME)) { return (B_TRUE); } assert(nvlist_exists(db_nvl, IPADM_NVP_PROTONAME)); /* * extract the propname from the `db_nvl' and also extract the * protocol from the `db_nvl'. */ for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(db_nvl, nvp)) { name = nvpair_name(nvp); if (strcmp(name, IPADM_NVP_PROTONAME) == 0) { if (nvpair_value_string(nvp, &protostr) != 0) return (B_TRUE); } else { assert(!IPADM_PRIV_NVP(name)); pname = name; if (nvpair_value_string(nvp, &pval) != 0) return (B_TRUE); } } /* if the private property is in the right format return */ if (strncmp(pname, IPADM_PERSIST_PRIVPROP_PREFIX, strlen(IPADM_PERSIST_PRIVPROP_PREFIX)) == 0) { return (B_TRUE); } /* if it's a public property move onto the next property */ nproto = proto = ipadm_str2proto(protostr); if (ipadm_legacy2new_propname(pname, nname, sizeof (nname), &nproto) != 0) { return (B_TRUE); } /* replace the old protocol with new protocol, if required */ if (nproto != proto) { protostr = ipadm_proto2str(nproto); if (nvlist_add_string(db_nvl, IPADM_NVP_PROTONAME, protostr) != 0) { return (B_TRUE); } } /* replace the old property name with new property name, if required */ /* add the prefix to property name */ (void) snprintf(tmpstr, sizeof (tmpstr), "_%s", nname); if (nvlist_add_string(db_nvl, tmpstr, pval) != 0 || nvlist_remove(db_nvl, pname, DATA_TYPE_STRING) != 0) { return (B_TRUE); } (void) memset(buf, 0, buflen); if (ipadm_nvlist2str(db_nvl, buf, buflen) == 0) { /* buffer overflow */ *errp = ENOBUFS; } return (B_TRUE); } /* * Called during boot. * * Walk through the DB and apply all the global module properties. We plow * through the DB even if we fail to apply property. */ /* ARGSUSED */ static boolean_t ipmgmt_db_init(void *cbarg, nvlist_t *db_nvl, char *buf, size_t buflen, int *errp) { ipadm_handle_t iph = cbarg; nvpair_t *nvp, *pnvp = NULL; char *strval = NULL, *name, *mod = NULL, *pname; char tmpstr[IPMGMT_STRSIZE]; uint_t proto; /* * We could have used nvl_exists() directly, however we need several * calls to it and each call traverses the list. Since this codepath * is exercised during boot, let's traverse the list ourselves and do * the necessary checks. */ for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(db_nvl, nvp)) { name = nvpair_name(nvp); if (IPADM_PRIV_NVP(name)) { if (strcmp(name, IPADM_NVP_IFNAME) == 0 || strcmp(name, IPADM_NVP_AOBJNAME) == 0) return (B_TRUE); else if (strcmp(name, IPADM_NVP_PROTONAME) == 0 && nvpair_value_string(nvp, &mod) != 0) return (B_TRUE); } else { /* possible a property */ pnvp = nvp; } } /* If we are here then we have found a global property */ assert(mod != NULL); assert(nvpair_type(pnvp) == DATA_TYPE_STRING); proto = ipadm_str2proto(mod); name = nvpair_name(pnvp); if (nvpair_value_string(pnvp, &strval) == 0) { if (strncmp(name, IPADM_PERSIST_PRIVPROP_PREFIX, strlen(IPADM_PERSIST_PRIVPROP_PREFIX)) == 0) { /* private protocol property */ pname = &name[1]; } else if (ipadm_legacy2new_propname(name, tmpstr, sizeof (tmpstr), &proto) == 0) { pname = tmpstr; } else { pname = name; } if (ipadm_set_prop(iph, pname, strval, proto, IPADM_OPT_ACTIVE) != IPADM_SUCCESS) { ipmgmt_log(LOG_WARNING, "Failed to reapply property %s", pname); } } return (B_TRUE); } /* initialize global module properties */ void ipmgmt_init_prop() { ipadm_handle_t iph = NULL; if (ipadm_open(&iph, IPH_INIT) != IPADM_SUCCESS) { ipmgmt_log(LOG_WARNING, "Could not reapply any of the " "persisted protocol properties"); return; } /* ipmgmt_db_init() logs warnings if there are any issues */ (void) ipmgmt_db_walk(ipmgmt_db_init, iph, IPADM_DB_READ); ipadm_close(iph); } void ipmgmt_release_scf_resources(scf_resources_t *res) { scf_entry_destroy(res->sr_ent); scf_transaction_destroy(res->sr_tx); scf_value_destroy(res->sr_val); scf_property_destroy(res->sr_prop); scf_pg_destroy(res->sr_pg); scf_instance_destroy(res->sr_inst); (void) scf_handle_unbind(res->sr_handle); scf_handle_destroy(res->sr_handle); } /* * It creates the necessary SCF handles and binds the given `fmri' to an * instance. These resources are required for retrieving property value, * creating property groups and modifying property values. */ int ipmgmt_create_scf_resources(const char *fmri, scf_resources_t *res) { res->sr_tx = NULL; res->sr_ent = NULL; res->sr_inst = NULL; res->sr_pg = NULL; res->sr_prop = NULL; res->sr_val = NULL; if ((res->sr_handle = scf_handle_create(SCF_VERSION)) == NULL) return (-1); if (scf_handle_bind(res->sr_handle) != 0) { scf_handle_destroy(res->sr_handle); return (-1); } if ((res->sr_inst = scf_instance_create(res->sr_handle)) == NULL) goto failure; if (scf_handle_decode_fmri(res->sr_handle, fmri, NULL, NULL, res->sr_inst, NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { goto failure; } /* we will create the rest of the resources on demand */ return (0); failure: ipmgmt_log(LOG_WARNING, "failed to create scf resources: %s", scf_strerror(scf_error())); ipmgmt_release_scf_resources(res); return (-1); } /* * persists the `pval' for a given property `pname' in SCF. The only supported * SCF property types are INTEGER and ASTRING. */ static int ipmgmt_set_scfprop_value(scf_resources_t *res, const char *pname, void *pval, scf_type_t ptype) { int result = -1; boolean_t new; if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) goto failure; switch (ptype) { case SCF_TYPE_INTEGER: scf_value_set_integer(res->sr_val, *(int64_t *)pval); break; case SCF_TYPE_ASTRING: if (scf_value_set_astring(res->sr_val, (char *)pval) != 0) { ipmgmt_log(LOG_WARNING, "Error setting string value %s " "for property %s: %s", pval, pname, scf_strerror(scf_error())); goto failure; } break; default: goto failure; } if ((res->sr_tx = scf_transaction_create(res->sr_handle)) == NULL) goto failure; if ((res->sr_ent = scf_entry_create(res->sr_handle)) == NULL) goto failure; if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) goto failure; retry: new = (scf_pg_get_property(res->sr_pg, pname, res->sr_prop) != 0); if (scf_transaction_start(res->sr_tx, res->sr_pg) == -1) goto failure; if (new) { if (scf_transaction_property_new(res->sr_tx, res->sr_ent, pname, ptype) == -1) { goto failure; } } else { if (scf_transaction_property_change(res->sr_tx, res->sr_ent, pname, ptype) == -1) { goto failure; } } if (scf_entry_add_value(res->sr_ent, res->sr_val) != 0) goto failure; result = scf_transaction_commit(res->sr_tx); if (result == 0) { scf_transaction_reset(res->sr_tx); if (scf_pg_update(res->sr_pg) == -1) { goto failure; } goto retry; } if (result == -1) goto failure; return (0); failure: ipmgmt_log(LOG_WARNING, "failed to save the data in SCF: %s", scf_strerror(scf_error())); return (-1); } /* * Given a `pgname'/`pname', it retrieves the value based on `ptype' and * places it in `pval'. */ static int ipmgmt_get_scfprop(scf_resources_t *res, const char *pgname, const char *pname, void *pval, scf_type_t ptype) { ssize_t numvals; scf_simple_prop_t *prop; prop = scf_simple_prop_get(res->sr_handle, IPMGMTD_FMRI, pgname, pname); numvals = scf_simple_prop_numvalues(prop); if (numvals <= 0) goto ret; switch (ptype) { case SCF_TYPE_INTEGER: *(int64_t **)pval = scf_simple_prop_next_integer(prop); break; case SCF_TYPE_ASTRING: *(char **)pval = scf_simple_prop_next_astring(prop); break; default: break; } ret: scf_simple_prop_free(prop); return (numvals); } /* * It stores the `pval' for given `pgname'/`pname' property group in SCF. */ static int ipmgmt_set_scfprop(scf_resources_t *res, const char *pgname, const char *pname, void *pval, scf_type_t ptype) { scf_error_t err; if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) { ipmgmt_log(LOG_WARNING, "failed to create property group: %s", scf_strerror(scf_error())); return (-1); } if (scf_instance_add_pg(res->sr_inst, pgname, SCF_GROUP_APPLICATION, 0, res->sr_pg) != 0) { if ((err = scf_error()) != SCF_ERROR_EXISTS) { ipmgmt_log(LOG_WARNING, "Error adding property group '%s/%s': %s", pgname, pname, scf_strerror(err)); return (-1); } /* * if the property group already exists, then we get the * composed view of the property group for the given instance. */ if (scf_instance_get_pg_composed(res->sr_inst, NULL, pgname, res->sr_pg) != 0) { ipmgmt_log(LOG_WARNING, "Error getting composed view " "of the property group '%s/%s': %s", pgname, pname, scf_strerror(scf_error())); return (-1); } } return (ipmgmt_set_scfprop_value(res, pname, pval, ptype)); } /* * Returns B_TRUE, if the non-global zone is being booted for the first time * after being installed. This is required to setup the ipadm data-store for * the first boot of the non-global zone. Please see, PSARC 2010/166, * for more info. * * Note that, this API cannot be used to determine first boot post image-update. * 'pkg image-update' clones the current BE and the existing value of * ipmgmtd/first_boot_done will be carried forward and obviously it will be set * to B_TRUE. */ boolean_t ipmgmt_ngz_firstboot_postinstall() { scf_resources_t res; boolean_t bval = B_TRUE; char *strval; /* we always err on the side of caution */ if (ipmgmt_create_scf_resources(IPMGMTD_FMRI, &res) != 0) return (bval); if (ipmgmt_get_scfprop(&res, IPMGMTD_APP_PG, IPMGMTD_PROP_FBD, &strval, SCF_TYPE_ASTRING) > 0) { bval = (strcmp(strval, IPMGMTD_TRUESTR) == 0 ? B_FALSE : B_TRUE); } else { /* * IPMGMTD_PROP_FBD does not exist in the SCF. Lets create it. * Since we err on the side of caution, we ignore the return * error and return B_TRUE. */ (void) ipmgmt_set_scfprop(&res, IPMGMTD_APP_PG, IPMGMTD_PROP_FBD, IPMGMTD_TRUESTR, SCF_TYPE_ASTRING); } ipmgmt_release_scf_resources(&res); return (bval); } /* * Returns B_TRUE, if the data-store needs upgrade otherwise returns B_FALSE. * Today we have to take care of, one case of, upgrading from version 0 to * version 1, so we will use boolean_t as means to decide if upgrade is needed * or not. Further, the upcoming projects might completely move the flatfile * data-store into SCF and hence we shall keep this interface simple. */ boolean_t ipmgmt_needs_upgrade(scf_resources_t *res) { boolean_t bval = B_TRUE; int64_t *verp; if (ipmgmt_get_scfprop(res, IPMGMTD_APP_PG, IPMGMTD_PROP_DBVER, &verp, SCF_TYPE_INTEGER) > 0) { if (*verp == IPADM_DB_VERSION) bval = B_FALSE; } /* * 'datastore_version' doesn't exist. Which means that we need to * upgrade the datastore. We will create 'datastore_version' and set * the version value to IPADM_DB_VERSION, after we upgrade the file. */ return (bval); } /* * This is called after the successful upgrade of the local data-store. With * the data-store upgraded to recent version we don't have to do anything on * subsequent reboots. */ void ipmgmt_update_dbver(scf_resources_t *res) { int64_t version = IPADM_DB_VERSION; (void) ipmgmt_set_scfprop(res, IPMGMTD_APP_PG, IPMGMTD_PROP_DBVER, &version, SCF_TYPE_INTEGER); } /* * Return TRUE if `ifname' has persistent configuration for the `af' address * family in the datastore. * It is possible to call the function with af == AF_UNSPEC, so in this case * the function returns TRUE if either AF_INET or AF_INET6 interface exists */ boolean_t ipmgmt_persist_if_exists(const char *ifname, sa_family_t af) { boolean_t exists = B_FALSE; nvlist_t *if_info_nvl; uint16_t *families = NULL; sa_family_t af_db; uint_t nelem = 0; if (ipmgmt_get_ifinfo_nvl(ifname, &if_info_nvl) != 0) goto done; if (nvlist_lookup_uint16_array(if_info_nvl, IPADM_NVP_FAMILIES, &families, &nelem) != 0) goto done; while (nelem-- > 0) { af_db = families[nelem]; if (af_db == af || (af == AF_UNSPEC && (af_db == AF_INET || af_db == AF_INET6))) { exists = B_TRUE; break; } } done: if (if_info_nvl != NULL) nvlist_free(if_info_nvl); return (exists); } /* * Retrieves the membership information for the requested mif_name * if mif_name is a member of a IPMP group, then gif_name will contain * the name of IPMP group interface, otherwise the variable will be empty */ void ipmgmt_get_group_interface(const char *mif_name, char *gif_name, size_t size) { char *gif_name_from_nvl; nvlist_t *if_info_nvl; gif_name[0] = '\0'; if (ipmgmt_get_ifinfo_nvl(mif_name, &if_info_nvl) != 0) goto done; if (nvlist_lookup_string(if_info_nvl, IPADM_NVP_GIFNAME, &gif_name_from_nvl) != 0) goto done; (void) strlcpy(gif_name, gif_name_from_nvl, size); done: if (if_info_nvl != NULL) nvlist_free(if_info_nvl); } static int ipmgmt_get_ifinfo_nvl(const char *ifname, nvlist_t **if_info_nvl) { ipmgmt_get_cbarg_t cbarg; nvpair_t *nvp; nvlist_t *nvl; int err; cbarg.cb_ifname = NULL; cbarg.cb_aobjname = NULL; cbarg.cb_ocnt = 0; *if_info_nvl = NULL; if ((err = nvlist_alloc(&cbarg.cb_onvl, NV_UNIQUE_NAME, 0)) != 0) goto done; err = ipmgmt_db_walk(ipmgmt_db_getif, &cbarg, IPADM_DB_READ); if (err == ENOENT && cbarg.cb_ocnt > 0) err = 0; if (err != 0) goto done; for (nvp = nvlist_next_nvpair(cbarg.cb_onvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(cbarg.cb_onvl, nvp)) { if (strcmp(nvpair_name(nvp), ifname) != 0) continue; if ((err = nvpair_value_nvlist(nvp, &nvl)) != 0 || (err = nvlist_dup(nvl, if_info_nvl, NV_UNIQUE_NAME)) != 0) *if_info_nvl = NULL; break; } done: nvlist_free(cbarg.cb_onvl); return (err); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * Utility functions used by the ipmgmtd daemon. */ #include #include #include #include #include #include #include #include "ipmgmt_impl.h" #define IPMGMT_BUFSIZ 1024 void ipmgmt_log(int pri, const char *fmt, ...) { va_list alist; va_start(alist, fmt); vsyslog(pri, fmt, alist); va_end(alist); } /* * Copy a source file to a new destination. The source file will be * removed if rdonly is false (i.e., used when the source file resides * on a read-only file system). * * Returns 0 on success and errno on failure. */ int ipmgmt_cpfile(const char *src, const char *dst, boolean_t rdonly) { struct stat statbuf; FILE *sfp, *dfp; char buf[IPMGMT_BUFSIZ]; int err = 0; errno = 0; /* * Attempt to open the destination file first since we * want to optimize for the case where it is read-only * and will return EROFS. */ if ((dfp = fopen(dst, "w+")) == NULL) return (errno); /* * Require that the source file exists. */ if (stat(src, &statbuf) != 0) { err = errno; (void) fclose(dfp); return (err); } if ((sfp = fopen(src, "r")) == NULL) { err = errno; (void) fclose(dfp); return (err); } /* * Copy the file. */ while (fgets(buf, sizeof (buf), sfp) != NULL && errno == 0) { (void) fputs(buf, dfp); if (errno != 0) break; } if (errno != 0) err = errno; else if (fflush(dfp) == EOF) err = errno; (void) fclose(sfp); (void) fclose(dfp); /* * If any error occurred, then remove the destination file. */ if (err != 0) { (void) unlink(dst); return (err); } /* * Make sure the file attributes are correct. */ if (chmod(dst, IPADM_FILE_MODE) != 0 || chown(dst, UID_NETADM, GID_NETADM) != 0) { err = errno; (void) unlink(dst); return (err); } /* * If the source file does not reside on a read-only file system * then remove it. */ if (!rdonly) (void) unlink(src); return (0); } #!/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 (c) 2010, Oracle and/or its affiliates. All rights reserved. # Copyright (c) 2012, Joyent, Inc. All rights reserved. # # This daemon stores address object to logical interface number mappings # (among other things) and reads/writes from/to ipmgmtd data store. # . /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 # # network/ip-interface-management:default service is always enabled by default. # When the non-global shared-IP stack zone boots, it tries to bring up this # service as well. If we don't start a background process and simply exit the # service, the service will go into maintenance mode and so will all it's # dependents. Ideally we would simply exit with SMF_EXIT_NODAEMON, but since # this method is also used in an S10C zone, where support for SMF_EXIT_NODAEMON # does not exist, we have to stick around. # # In S10C zone (where this script is also used) smf_isnonglobalzone # function is unavailable in smf_include.sh # if [ `/sbin/zonename` != global ]; then if [ `/sbin/zonename -t` = shared ]; then (while true ; do sleep 3600 ; done) & exit $SMF_EXIT_OK fi fi # # We must be now in a global zone or non-global zone with exclusive-IP stack. # Start the ipmgmtd daemon. # if /lib/inet/ipmgmtd ; 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. # # usr/src/cmd/cmd-inet/lib/netcfgd/Makefile # PROG= netcfgd include ../../../Makefile.cmd ROOTCMDDIR= $(ROOT)/lib/inet LDLIBS += -lnwam -lumem .KEEP_STATE: .PARALLEL: all: $(PROG) install: all $(ROOTCMD) 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 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ /* * netcfgd - network configuration daemon. At present, this daemon implements * the configuration backend for libnwam (via calls to nwam_backend_init() * and nwam_backend_fini()). Initialization of the backend creates a door * that libnwam calls use to read, update and destroy persistent configuration. * * More long-term, netcfgd will be used to manage other sources of configuration * data and the backend functionality currently contained in libnwam will be * generalized. */ #include #include #include #include #include #include #include #include #include #include #include #include #include static const char *progname; static boolean_t fg = B_FALSE; /* * This function allows you to drop a dtrace probe here and trace * complete strings (not just those containing formatting). It's * important that we actually format the strings so we could trace them * even if we choose not to log them. */ static void log_out(int severity, const char *str) { if (fg) { (void) fprintf(stderr, "%s: %s\n", progname, str); } else { syslog(severity, str); } } /* PRINTFLIKE2 */ void nlog(int severity, const char *fmt, ...) { va_list ap; char *vbuf; va_start(ap, fmt); if (vasprintf(&vbuf, fmt, ap) != -1) { log_out(severity, vbuf); free(vbuf); } va_end(ap); } static void start_logging(void) { if (!fg) openlog(progname, LOG_PID, LOG_DAEMON); nlog(LOG_DEBUG, "%s started", progname); } 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()) == -1) { nlog(LOG_ERR, "fork 1 failed"); exit(EXIT_FAILURE); } if (pid != 0) { (void) wait(NULL); nlog(LOG_DEBUG, "child %ld exited, daemonizing", pid); _exit(0); } if (setsid() == (pid_t)-1) { nlog(LOG_ERR, "setsid"); exit(EXIT_FAILURE); } if ((pid = fork()) == -1) { nlog(LOG_ERR, "fork 2 failed"); exit(EXIT_FAILURE); } if (pid != 0) { _exit(0); } (void) chdir("/"); (void) umask(022); } /* ARGSUSED */ static void graceful_shutdown(int signo) { nwam_backend_fini(); exit(EXIT_SUCCESS); } int main(int argc, char *argv[]) { int c; nwam_error_t err; if ((progname = strrchr(argv[0], '/')) == NULL) progname = argv[0]; else progname++; while ((c = getopt(argc, argv, "f")) != -1) { switch (c) { case 'f': fg = B_TRUE; break; default: (void) fprintf(stderr, "%s: unrecognized option %c\n", progname, optopt); exit(EXIT_FAILURE); } } start_logging(); if (!fg) daemonize(); (void) signal(SIGTERM, graceful_shutdown); (void) signal(SIGQUIT, graceful_shutdown); (void) signal(SIGPIPE, SIG_IGN); (void) signal(SIGCHLD, SIG_IGN); (void) atexit(nwam_backend_fini); if ((err = nwam_backend_init()) != NWAM_SUCCESS) { nlog(LOG_ERR, "couldn't initialize libnwam backend: %s", nwam_strerror(err)); exit(EXIT_FAILURE); } for (;;) (void) pause(); /* NOTREACHED */ return (EXIT_SUCCESS); } # # 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) 2007, 2010, Oracle and/or its affiliates. All rights reserved. # # Copyright (c) 2018, Joyent, Inc. # Needed for ROOTFS_LIBDIR definition include ../../../../lib/Makefile.lib PROG= nwamd OBJS= conditions.o dlpi_events.o door_if.o enm.o\ events.o known_wlans.o llp.o loc.o logging.o\ main.o ncp.o ncu.o ncu_phys.o ncu_ip.o objects.o\ routing_events.o sysevent_events.o util.o SRCS= $(OBJS:%.o=%.c) HEADERS= conditions.h events.h known_wlans.h llp.h ncp.h ncu.h\ objects.h LOCFILES= create_loc_auto create_loc_nonet NONETLOCFILES= ipf.conf.dfl ipf6.conf.dfl ROOTCFGDIR= $(ROOTETC)/nwam ROOTLOCDIR= $(ROOTCFGDIR)/loc NONETLOCDIR= $(ROOTLOCDIR)/NoNet LOCDIRS= $(NONETLOCDIR) ROOTCFGFILES= $(LOCFILES:%=$(ROOTLOCDIR)/%) \ $(NONETLOCFILES:%=$(NONETLOCDIR)/%) include ../../../Makefile.cmd $(ROOTCFGFILES) : FILEMODE= 644 ROOTCMDDIR= $(ROOTFS_LIBDIR)/inet LDLIBS += -ldhcpagent -ldhcputil -ldladm -ldlpi -lgen \ -linetutil -lipadm -lkstat -lnsl -lnvpair -lnwam \ -lsecdb -lscf -lsocket -lsysevent -lumem -luutil CERRWARN += -Wno-parentheses CERRWARN += -Wno-switch # not linted SMATCH=off # # Instrument with CTF data to ease debugging. # CTFCONVERT_HOOK = && $(CTFCONVERT_O) CTFMERGE_HOOK = && $(CTFMERGE) -L VERSION -o $@ $(OBJS) $(OBJS) : CFLAGS += $(CTF_FLAGS) .KEEP_STATE: .PARALLEL: all: $(PROG) $(PROG): $(OBJS) $(LINK.c) $(OBJS) -o $@ $(LDLIBS) $(CTFMERGE_HOOK) $(POST_PROCESS) install: $(ROOTCMD) $(ROOTLOCDIR) $(ROOTCFGFILES) check: $(SRCS) $(HEADERS) $(CSTYLE) -cpP $(SRCS) $(HEADERS) $(ROOTCMD): all clean: $(RM) $(OBJS) $(ROOTCFGDIR): $(INS.dir) $(ROOTLOCDIR): $(ROOTCFGDIR) $(INS.dir) $(LOCDIRS): $(ROOTLOCDIR) $(INS.dir) # Hammerhead: order-only prerequisite so $< is the file, not the directory $(ROOTLOCDIR)/%: % | $(ROOTLOCDIR) $(INS.file) # Hammerhead: order-only prerequisite so $< is the file, not the directory $(NONETLOCDIR)/%: % | $(NONETLOCDIR) $(INS.file) 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 2010 Sun Microsystems, Inc. All rights reserved. Use is subject to license terms. Implementation Overview for the NetWork AutoMagic daemon John Beck, Renee Danson, Michael Hunter, Alan Maguire, Kacheong Poon, Garima Tripathi, Jan Xie, Anurag Maskey [Structure and some content shamelessly stolen from Peter Memishian's dhcpagent architecture overview.] INTRODUCTION ============ Details about the NWAM requirements, architecture, and design are available via the NWAM opensolaris project at http://opensolaris.org/os/project/nwam. The point of this document is to place details relevant to somebody attempting to understand the implementation close to the source code. THE BASICS ========== SOURCE FILE ORGANIZATION ======================= event sources: dlpi_events.c routing_events.c sysevent_events.c object-specific event handlers: enm.c known_wlans.c loc.c ncp.c ncu_ip.c ncu_phys.c legacy config upgrade llp.c generic code: objects.c events.c conditions.c logging.c util.c nwam door requests: door_if.c entry point: main.c OVERVIEW ======== Here we discuss the essential objects and subtle aspects of the NWAM daemon implementation. Note that there is of course much more that is not discussed here, but after this overview you should be able to fend for yourself in the source code. Events and Objects ================== Events come to NWAM from a variety of different sources asyncronously. o routing socket o dlpi o sysevents o doors Routing sockets and dlpi (DL_NOTE_LINK_UP|DOWN events) are handled by dedicated threads. Sysevents and doors are both seen as callbacks into the process proper and will often post their results to the main event queue. All event sources post events onto the main event queue. In addition state changes of objects and door requests (requesting current state or a change of state, specification of a WiFi key etc) can lead to additional events. We have daemon-internal events (object initialization, periodic state checks) which are simply enqueued on the event queue, and external events which are both enqueued on the event queue and sent to registered listeners (via nwam_event_send()). So the structure of the daemon is a set of threads that drive event generation. Events are posted either directly onto the event queue or are delayed by posting onto the pending event queue. SIGALARMs are set for the event delay, and when the SIGALARM is received pending events that have expired are moved onto the event queue proper. Delayed enqueueing is useful for periodic checks. Decisions to change conditions based upon object state changes are delayed until after bursts of events. This is achieved by marking a flag when it is deemed checking is necessary and then the next time the queue is empty performing those checks. A typical event profile will be one event (e.g. a link down) causing a flurry of other events (e.g. related interface down). By waiting until all the consequences of the initial event have been carried out to make higher level decisions we implicitly debounce those higher level decisions. At the moment queue quiet actually means that the queue has been quiet for some short period of time (.1s). Typically the flurry of events we want to work through are internally generated and are back to back in the queue. We wait a bit longer in case there are reprucussions from what we do that cause external events to be posted on us. We are not interested in waiting for longer term things to happen but merely to catch immediate changes. When running, the daemon will consist of a number of threads: o the event handling thread: a thread blocking until events appear on the event queue, processing each event in order. Events that require time-consuming processing are spawned in worker threads (e.g. WiFi connect, DHCP requests etc). o door request threads: the door infrastructure manages server threads which process synchronous NWAM client requests (e.g. get state of an object, connect to a specific WLAN, initiate a scan on a link etc). o various wifi/IP threads: threads which do asynchronous work such as DHCP requests, WLAN scans etc that cannot hold up event processing in the main event handling thread. o routing socket threads: process routing socket messages of interest (address additons/deletions) and package them as NWAM messages. o dlpi threads: used to monitor for DL_NOTE_LINK messages on links The daemon is structured around a set of objects representing NCPs[1], NCUs[2], ENMs[3] and known WLANs and a set of state machines which consume events which act on those objects. Object lists are maintained for each object type, and these contain both a libnwam handle (to allow reading the object directly) and an optional object data pointer which can point to state information used to configure the object. Events can be associated with specific objects (e.g. link up), or associated with no object in particular (e.g. shutdown). Each object type registers a set of event handler functions with the event framework such that when an event occurs, the appropriate handler for the object type is used. The event handlers are usually called nwamd_handle_*_event(). [1] NCP Network Configuration Profile; the set of link- and IP-layer configuration units which collectively specify how a system should be connected to the network [2] NCU Network Configuration Unit; the individual components of an NCP [3] ENM External Network Modifiers; user executable scripts often used to configure a VPN Doors and External Events ========================= The command interface to nwamd is thread a door at NWAM_DOOR (/etc/svc/volatile/nwam/nwam_door). This door allows external program to send messages to nwamd. The way doors work is to provide a mechanism for another process to execute code in your process space. This looks like a CSPish send/receive/reply in that the receiving process provide a syncronization point (via door_create(3C)), the calling process uses that syncronization point to rendezvous with and provide arguments (via door_call(3C), and then the receive process reply (via door_return(3C))) passing back data as required. The OS makes it such that the memory used to pass data via door_call(3C) is mapped into the receiving process which can write back into it and then transparently have it mapped back to the calling process. As well as handling internal events of interest, the daemon also needs to send events of interest (link up/down, WLAN scan/connect results etc) to (possibly) multiple NWAM client listeners. This is done via System V message queues. On registering for events via a libnwam door request into the daemon (nwam_events_register()), a per-client (identified by pid) message queue file is created. The daemon sends messages to all listeners by examining the list of message queue files (allowing registration to be robust across daemon restarts) and sending events to each listener. This is done via the libnwam function nwam_event_send() which hides the IPC mechanism from the daemon. Objects ======= Four object lists are maintained within the daemon - one each for the configuration objects libnwam manages. i.e.: o ENMs o locations o known WLANs o NCUs of the current active NCP Objects have an associated libnwam handle and an optional data field (which is used for NCUs only). Locking is straightforward - nwamd_object_init() will initialize an object of a particular type in the appropriate object list, returning it with the object lock held. When it is no longer needed, nwamd_object_unlock() should be called on the object. To retrieve an existing object, nwamd_object_find() should be called - again this returns the object in a locked state. nwamd_object_lock() is deliberately not exposed outside of objects.c, since object locking is implicit in the above creation/retrieval functions. An object is removed from the object list (with handle destroyed) via nwamd_object_fini() - the object data (if any) is returned from this call to allow deallocation. Object state ============ nwamd deals with 3 broad types of object that need to maintain internal state: NCUs, ENMs and locations (known WLANs are configuration objects but don't have a state beyond simply being present). NWAM objects all share a basic set of states: State Description ===== =========== uninitialized object representation not present on system or in nwamd initialized object representation present in system and in nwamd disabled disabled manually offline external conditions are not satisfied offline* external conditions are satisfied, trying to move online online* external conditions no longer satisfied, trying to move offline online conditions satisfied and configured maintenance error occurred in applying configuration These deliberately mimic SMF states. The states of interest are offline, offline* and online. An object (link/interface NCU, ENM or location) should only move online when its conditions are satisfied _and_ its configuration has been successfully applied. This occurs when an ENM method has run or a link is up, or an interface has at least one address assigned. To understand the distinction between offline and offline*, consider the case where a link is of prioritized activation, and either is a lower priority group - and hence inactive (due to cable being unplugged or inability to connect to wifi) - or a higher priority group - and hence active. In general, we want to distinguish between two cases: 1) when we are actively configuring the link with a view to moving online (offline*), as would be the case when the link's priority group is active. 2) when external policy-based conditions prevent a link from being active. offline should be used for such cases. Links in priority groups above and below the currently-active group will be offline, since policy precludes them from activating (as less-prioritized links). So we see that offline and offline* can thus be used to distinguish between cases that have the potentiality to move online (offline*) from a policy perspective - i.e. conditions on the location allow it, or link prioritization allows it - and cases where external conditions dictate that it should not (offline). Once an object reaches offline*, its configuration processes should kick in. This is where auxiliary state is useful, as it allows us to distinguish between various states in that configuration process. For example, a link can be waiting for WLAN selection or key data, or an interface can be waiting for DHCP response. This auxiliary state can then also be used diagnostically by libnwam consumers to determine the current status of a link, interface, ENM etc. WiFi links present a problem however. On the one hand, we want them to be inactive when they are not part of the current priority grouping, while on the other we want to watch out for new WLANs appearing in scan data if the WiFi link is of a higher priority than the currently-selected group. The reason we watch out for these is they represent the potential to change priority grouping to a more preferred group. To accommodate this, WiFi links of the same or lower (more preferred) priority group will always be trying to connect (and thus be offline* if they cannot). It might appear unnecessary to have a separate state value/machine for auxiliary state - why can't we simply add the auxiliary state machine to the global object state machine? Part of the answer is that there are times we need to run through the same configuration state machine when the global object state is different - in paticular either offline* or online. Consider WiFi - we want to do periodic scans to find a "better" WLAN - we can easily do this by running back through the link state machine of auxiliary states, but we want to stay online while we do it, since we are still connected (if the WLAN disconnects of course we go to LINK_DOWN and offline). Another reason we wish to separate the more general states (offline, online etc) from the more specific ones (WIFI_NEED_SELECTION etc) is to ensure that the representation of configuration objects closely matches the way SMF works. For an NCU physical link, the following link-specific auxiliary states are used: Auxiliary state Description =============== =========== LINK_WIFI_SCANNING Scan in progress LINK_WIFI_NEED_SELECTION Need user to specify WLAN LINK_WIFI_NEED_KEY Need user to specify a WLAN key for selection LINK_WIFI_CONNECTING Connecting to current selection A WiFI link differs from a wired one in that it always has the potential to be available - it just depends if visited WLANs are in range. So such links - if they are higher in the priority grouping than the currently-active priority group - should always be able to scan, as they are always "trying" to be activated. Wired links that do not support DL_NOTE_LINK_UP/DOWN are problematic, since we have to simply assume a cable is plugged in. If an IP NCU is activated above such a link, and that NCU uses DHCP, a timeout will be triggered eventually (user-configurable via the nwamd/ncu_wait_time SMF property of the network/physical:nwam instance) which will cause us to give up on the link. For an IP interface NCU, the following auxiliary states are suggested. Auxiliary state Description =============== =========== NWAM_AUX_STATE_IF_WAITING_FOR_ADDR Waiting for an address to be assigned NWAM_AUX_STATE_IF_DHCP_TIMED_OUT DHCP timed out on interface A link can have multiple logical interfaces plumbed on it consisting of a mix of static and DHCP-acquired addresses. This means that we need to decide how to aggregate the state of these logical interfaces into the NCU state. The concept of "up" we use here does not correspond to IFF_UP or IFF_RUNNING, but rather when we get (via getting RTM_NEWADDR events with non-zero addresses) at least one address assigned to the link. We use this concept of up as it represents the potential for network communication - e.g. after assigning a static address, if the location specifies nameserver etc, it is possible to communicate over the network. One important edge case here is that when DHCP information comes in, we need to reassess location activation conditions and possibly change or reapply the current location. The problem is that if we have a static/DHCP mix, and if we rely on the IP interface's notion of "up" to trigger location activation, we will likely first apply the location when the static address has been assigned and before the DHCP information has been returned (which may include nameserver info). So the solution is that on getting an RTM_NEWADDR, we check if the (logical) interface associated is DHCP, and even if the interface NCU is already up, we reassess location activation. This will lead to a reapplication of the current location or possibly a location switch. In order to move through the various states, a generic API is supplied nwam_error_t nwamd_object_set_state(nwamd_object_t obj, nwamd_state_t state, nwamd_aux_state_t aux_state); This function creates an OBJECT_STATE event containing the new state/aux_state and enqueues it in the event queue. Each object registers its own handler for this event, and in response to the current state/aux state and desired aux state it responds appropriately in the event handling thread, spawning other threads to carry out actions as appropriate. The object state event is then sent to any registered listeners. So for NCUs, we define a handle_object_state() function to run the state machine for the NCU object. Link state and NCP policy ========================= NCPs can be either: o prioritized: where the constituent link NCUs specify priority group numbers (where lower are more favoured) and grouping types. These are used to allow link NCUs to be either grouped separately (exclusive) or together (shared or all). o manual: their activation is governed by the value of their enabled property. o a combination of the above. IP interface NCUs interit their activation from the links below them, so an IP interface NCU will be active if its underlying link is (assuming it hasn't been disabled). At startup, and at regular intervals (often triggered by NWAM events), the NCP policy needs to be reassessed. There are a number of causes for NCP policy to be reassessed - o a periodic check of link state that occurs every N seconds o a link goes from offline(*) to online (cable plug/wifi connect) o a link goes from online to offline (cable unplug/wifi disconnect). Any of these should cause the link selecton algorithm to rerun. The link selection algorithm works as follows: Starting from the lowest priority grouping value, assess all links in that priority group. The current priority-group is considered failed if: o "exclusive" NCUs exist and none are offline*/online, o "shared" NCUs exist and none are offline*/online, o "all" NCUs exist and all are not offline*/online, o no NCUs are offline*/online. We do not invalidate a link that is offline* since its configuration is in progress. This has the unfortunate side-effect that wired links that do not do DL_NOTE_LINK_UP/DOWN will never fail. If such links wish to be skipped, their priority group value should be increased (prioritizing wireless links). One a priority group has been selected, all links in groups above _and_ below it need to be moved offline. Location Activation =================== A basic set of system-supplied locations are supplied - NoNet and Automatic. nwamd will apply the NoNet location until such a time as an interface NCU is online, at which point it will switch to the Automatic location. If a user-supplied location is supplied, and it is either manually enabled or its conditions are satisfied, it will be preferred and activated instead. Only one location can be active at once since each location has its own specification of nameservices etc. ENM Activation ============== ENMs are either manual or conditional in activation and will be activated if they are enabled (manual) or if the conditions are met (conditional). Multiple ENMs can be active at once. /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "conditions.h" #include "ncu.h" #include "objects.h" #include "util.h" /* * conditions.c - contains routines which check state to see if activation * conditions for NWAM objects are satisfied and rates activation conditions to * help determine which is most specific. * * If the activation-mode is CONDITIONAL_ANY or CONDITIONAL_ALL, the conditions * property is set to a string made up of conditional expressions. Each * expression is made up of a condition that can be assigned a boolean value, * e.g. "system-domain is sun.com" or "ncu ip:bge0 is-not active". If the * activation-mode is CONDITIONAL_ANY, the condition will be satisfied if any * one of the conditions is true; if the activation-mode is CONDITIONAL_ALL, * the condition is satisfied only if all of the conditions are true. */ uint64_t condition_check_interval = CONDITION_CHECK_INTERVAL_DEFAULT; extern int getdomainname(char *, int); /* NCP, NCU, ENM and location conditions */ static boolean_t test_condition_ncp(nwam_condition_t condition, const char *ncp_name); static boolean_t test_condition_ncu(nwam_condition_t condition, const char *ncu_name); static boolean_t test_condition_enm(nwam_condition_t condition, const char *enm_name); static boolean_t test_condition_loc(nwam_condition_t condition, const char *loc_name); /* IP address conditions */ static boolean_t test_condition_ip_address(nwam_condition_t condition, const char *ip_address); /* domainname conditions */ static boolean_t test_condition_sys_domain(nwam_condition_t condition, const char *domainname); static boolean_t test_condition_adv_domain(nwam_condition_t condition, const char *domainname); /* WLAN conditions */ static boolean_t test_condition_wireless_essid(nwam_condition_t condition, const char *essid); static boolean_t test_condition_wireless_bssid(nwam_condition_t condition, const char *essid); struct nwamd_condition_map { nwam_condition_object_type_t object_type; boolean_t (*condition_func)(nwam_condition_t, const char *); } condition_map[] = { { NWAM_CONDITION_OBJECT_TYPE_NCP, test_condition_ncp }, { NWAM_CONDITION_OBJECT_TYPE_NCU, test_condition_ncu }, { NWAM_CONDITION_OBJECT_TYPE_ENM, test_condition_enm }, { NWAM_CONDITION_OBJECT_TYPE_LOC, test_condition_loc }, { NWAM_CONDITION_OBJECT_TYPE_IP_ADDRESS, test_condition_ip_address }, { NWAM_CONDITION_OBJECT_TYPE_SYS_DOMAIN, test_condition_sys_domain }, { NWAM_CONDITION_OBJECT_TYPE_ADV_DOMAIN, test_condition_adv_domain }, { NWAM_CONDITION_OBJECT_TYPE_ESSID, test_condition_wireless_essid }, { NWAM_CONDITION_OBJECT_TYPE_BSSID, test_condition_wireless_bssid } }; /* * This function takes which kind of conditions (is or is not) we are testing * the object against and an object and applies the conditon to the object. */ static boolean_t test_condition_object_state(nwam_condition_t condition, nwam_object_type_t object_type, const char *object_name) { nwamd_object_t object; nwam_state_t state; object = nwamd_object_find(object_type, object_name); if (object == NULL) return (B_FALSE); state = object->nwamd_object_state; nwamd_object_release(object); switch (condition) { case NWAM_CONDITION_IS: return (state == NWAM_STATE_ONLINE); case NWAM_CONDITION_IS_NOT: return (state != NWAM_STATE_ONLINE); default: return (B_FALSE); } } static boolean_t test_condition_ncp(nwam_condition_t condition, const char *name) { boolean_t active; (void) pthread_mutex_lock(&active_ncp_mutex); active = (strcasecmp(active_ncp, name) == 0); (void) pthread_mutex_unlock(&active_ncp_mutex); switch (condition) { case NWAM_CONDITION_IS: return (active); case NWAM_CONDITION_IS_NOT: return (active != B_TRUE); default: return (B_FALSE); } } static boolean_t test_condition_ncu(nwam_condition_t condition, const char *name) { char *real_name, *ncu_name; nwam_ncu_handle_t ncuh; nwam_ncu_type_t ncu_type; boolean_t rv; /* names are case-insensitive, so get real name from libnwam */ if (nwam_ncu_read(active_ncph, name, NWAM_NCU_TYPE_INTERFACE, 0, &ncuh) == NWAM_SUCCESS) { ncu_type = NWAM_NCU_TYPE_INTERFACE; } else if (nwam_ncu_read(active_ncph, name, NWAM_NCU_TYPE_LINK, 0, &ncuh) == NWAM_SUCCESS) { ncu_type = NWAM_NCU_TYPE_LINK; } else { return (B_FALSE); } if (nwam_ncu_get_name(ncuh, &real_name) != NWAM_SUCCESS) { nwam_ncu_free(ncuh); return (B_FALSE); } nwam_ncu_free(ncuh); /* * Name may be either unqualified or qualified by NCU type * (interface:/link:). Need to translate unqualified names * to qualified, specifying interface:name if an interface * NCU is present, otherwise link:ncu. */ if (nwam_ncu_name_to_typed_name(real_name, ncu_type, &ncu_name) != NWAM_SUCCESS) { free(real_name); return (B_FALSE); } free(real_name); rv = test_condition_object_state(condition, NWAM_OBJECT_TYPE_NCU, ncu_name); free(ncu_name); return (rv); } static boolean_t test_condition_enm(nwam_condition_t condition, const char *enm_name) { nwam_enm_handle_t enmh; char *real_name; boolean_t rv; /* names are case-insensitive, so get real name from libnwam */ if (nwam_enm_read(enm_name, 0, &enmh) != NWAM_SUCCESS) return (B_FALSE); if (nwam_enm_get_name(enmh, &real_name) != NWAM_SUCCESS) { nwam_enm_free(enmh); return (B_FALSE); } nwam_enm_free(enmh); rv = test_condition_object_state(condition, NWAM_OBJECT_TYPE_ENM, real_name); free(real_name); return (rv); } static boolean_t test_condition_loc(nwam_condition_t condition, const char *loc_name) { nwam_loc_handle_t loch; char *real_name; boolean_t rv; /* names are case-insensitive, so get real name from libnwam */ if (nwam_loc_read(loc_name, 0, &loch) != NWAM_SUCCESS) return (B_FALSE); if (nwam_loc_get_name(loch, &real_name) != NWAM_SUCCESS) { nwam_loc_free(loch); return (B_FALSE); } nwam_loc_free(loch); rv = test_condition_object_state(condition, NWAM_OBJECT_TYPE_LOC, real_name); free(real_name); return (rv); } static boolean_t test_condition_domain(nwam_condition_t condition, const char *target_domain, const char *found_domain) { int i, len_t, len_f; char target[MAXHOSTNAMELEN], found[MAXHOSTNAMELEN]; len_t = target_domain == NULL ? 0 : strlen(target_domain); len_f = found_domain == NULL ? 0 : strlen(found_domain); /* convert target_domain and found_domain to lowercase for strstr() */ for (i = 0; i < len_t; i++) target[i] = tolower(target_domain[i]); target[len_t] = '\0'; for (i = 0; i < len_f; i++) found[i] = tolower(found_domain[i]); found[len_f] = '\0'; switch (condition) { case NWAM_CONDITION_IS: return (found_domain != NULL && strcmp(found, target) == 0); case NWAM_CONDITION_IS_NOT: return (found_domain == NULL || strcmp(found, target) != 0); case NWAM_CONDITION_CONTAINS: return (found_domain != NULL && strstr(found, target) != NULL); case NWAM_CONDITION_DOES_NOT_CONTAIN: return (found_domain == NULL || strstr(found, target) == NULL); default: return (B_FALSE); } } struct ncu_adv_domains { struct ncu_adv_domains *next; char *dns_domain; char *nis_domain; }; static int get_adv_domains(nwamd_object_t obj, void *arg) { nwamd_ncu_t *ncu = (nwamd_ncu_t *)obj->nwamd_object_data; struct ncu_adv_domains **headpp = (struct ncu_adv_domains **)arg; struct ncu_adv_domains *adp; char *dns, *nis; if (ncu->ncu_type != NWAM_NCU_TYPE_INTERFACE) return (0); dns = nwamd_get_dhcpinfo_data("DNSdmain", ncu->ncu_name); nis = nwamd_get_dhcpinfo_data("NISdmain", ncu->ncu_name); if (dns != NULL || nis != NULL) { adp = (struct ncu_adv_domains *)malloc(sizeof (*adp)); if (adp == NULL) return (1); adp->dns_domain = dns; adp->nis_domain = nis; adp->next = *headpp; *headpp = adp; } return (0); } static boolean_t test_condition_sys_domain(nwam_condition_t condition, const char *domainname) { char cur_domainname[MAXHOSTNAMELEN]; if (getdomainname(cur_domainname, MAXHOSTNAMELEN) != 0) return (B_FALSE); return (test_condition_domain(condition, domainname, cur_domainname)); } static boolean_t test_condition_adv_domain(nwam_condition_t condition, const char *domainname) { struct ncu_adv_domains *adv_domains = NULL; struct ncu_adv_domains *adp, *prev; boolean_t positive, rtn; (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, get_adv_domains, &adv_domains); positive = (condition == NWAM_CONDITION_IS || condition == NWAM_CONDITION_CONTAINS); /* * Walk the advertised domain list. Our test function tests one * single domain, but we're dealing with a list: if our condition * is positive ('is' or 'contains'), the test function for each * domain results are or'd together; if our condition is negative * ('is-not' or 'does-not-contain'), the test function results must * be and'd. Thus our short-circuit exit value depends on our * condition: if the test function returns TRUE it implies immediate * success for a positive condition; if it returns FALSE it implies * immediate failure for a negative condition. */ adp = adv_domains; while (adp != NULL) { if ((test_condition_domain(condition, domainname, adp->dns_domain) == positive) || (test_condition_domain(condition, domainname, adp->nis_domain) == positive)) { rtn = positive; break; } adp = adp->next; } if (adp == NULL) { /* * We did not short-circuit; we therefore failed if our * condition was positive, and succeeded if our condition * was negative. */ rtn = !positive; } /* now free the domain list */ adp = adv_domains; while (adp != NULL) { prev = adp; adp = prev->next; free(prev->dns_domain); free(prev->nis_domain); free(prev); } return (rtn); } /* * Returns true if prefixlen bits of addr1 match prefixlen bits of addr2. */ static boolean_t prefixmatch(uchar_t *addr1, uchar_t *addr2, int prefixlen) { uchar_t mask[IPV6_ABITS/8]; int i, j = 0; if (prefixlen == 0) return (B_TRUE); while (prefixlen > 0) { if (prefixlen >= 8) { mask[j++] = 0xFF; prefixlen -= 8; } else { mask[j] |= 1 << (8 - prefixlen); prefixlen--; } } /* Ensure at least one byte is tested */ if (j == 0) j++; for (i = 0; i < j; i++) { if ((addr1[i] & mask[i]) != (addr2[i] & mask[i])) return (B_FALSE); } return (B_TRUE); } /* * Given a string representation of an IPv4 or IPv6 address returns the * sockaddr representation. Note that 'sockaddr' should point at the correct * sockaddr structure for the address family (sockaddr_in for AF_INET or * sockaddr_in6 for AF_INET6) or alternatively at a sockaddr_storage * structure. */ static struct sockaddr_storage * nwamd_str2sockaddr(sa_family_t af, const char *straddr, struct sockaddr_storage *addr) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; int err; if (af == AF_INET) { sin = (struct sockaddr_in *)addr; sin->sin_family = AF_INET; err = inet_pton(AF_INET, straddr, &sin->sin_addr); } else if (af == AF_INET6) { sin6 = (struct sockaddr_in6 *)addr; sin6->sin6_family = AF_INET6; err = inet_pton(AF_INET6, straddr, &sin6->sin6_addr); } else { errno = EINVAL; return (NULL); } return (err == 1 ? addr : NULL); } struct nwamd_ipaddr_condition_walk_arg { nwam_condition_t condition; struct sockaddr_storage sockaddr; int prefixlen; boolean_t res; }; static int check_ipaddr(sa_family_t family, struct ifaddrs *ifa, void *arg) { struct nwamd_ipaddr_condition_walk_arg *wa = arg; struct sockaddr_in6 addr6; struct sockaddr_in addr; boolean_t match = B_FALSE; uchar_t *addr1, *addr2; if (family == AF_INET) { (void) memcpy(&addr, ifa->ifa_addr, sizeof (addr)); addr1 = (uchar_t *)(&addr.sin_addr.s_addr); addr2 = (uchar_t *)&(((struct sockaddr_in *) &(wa->sockaddr))->sin_addr.s_addr); } else { (void) memcpy(&addr6, ifa->ifa_addr, sizeof (addr6)); addr1 = (uchar_t *)(&addr6.sin6_addr.s6_addr); addr2 = (uchar_t *)&(((struct sockaddr_in6 *) &(wa->sockaddr))->sin6_addr.s6_addr); } match = prefixmatch(addr1, addr2, wa->prefixlen); nlog(LOG_DEBUG, "check_ipaddr: match %d\n", match); switch (wa->condition) { case NWAM_CONDITION_IS: case NWAM_CONDITION_IS_IN_RANGE: wa->res = match; if (match) return (1); return (0); case NWAM_CONDITION_IS_NOT: case NWAM_CONDITION_IS_NOT_IN_RANGE: wa->res = !match; return (0); default: return (0); } } static boolean_t test_condition_ip_address(nwam_condition_t condition, const char *ip_address_string) { sa_family_t family; char *copy, *ip_address, *prefixlen_string, *lasts; struct nwamd_ipaddr_condition_walk_arg wa; struct ifaddrs *ifap, *ifa; if ((copy = strdup(ip_address_string)) == NULL) return (B_FALSE); if ((ip_address = strtok_r(copy, " \t/", &lasts)) == NULL) { free(copy); return (B_FALSE); } prefixlen_string = strtok_r(NULL, " \t", &lasts); if (nwamd_str2sockaddr(AF_INET, ip_address, &wa.sockaddr) != NULL) { family = AF_INET; wa.prefixlen = IP_ABITS; } else if (nwamd_str2sockaddr(AF_INET6, ip_address, &wa.sockaddr) != NULL) { family = AF_INET6; wa.prefixlen = IPV6_ABITS; } else { nlog(LOG_ERR, "test_condition_ip_address: " "nwamd_str2sockaddr failed for %s: %s", ip_address, strerror(errno)); free(copy); return (B_FALSE); } if (prefixlen_string != NULL) wa.prefixlen = atoi(prefixlen_string); wa.condition = condition; switch (condition) { case NWAM_CONDITION_IS: case NWAM_CONDITION_IS_IN_RANGE: wa.res = B_FALSE; break; case NWAM_CONDITION_IS_NOT: case NWAM_CONDITION_IS_NOT_IN_RANGE: wa.res = B_TRUE; break; default: free(copy); return (B_FALSE); } free(copy); if (getifaddrs(&ifa) == -1) { nlog(LOG_ERR, "test_condition_ip_address: " "getifaddrs failed: %s", strerror(errno)); return (wa.res); } for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) { if (ifap->ifa_addr->sa_family != family) continue; if (check_ipaddr(family, ifap, &wa) == 1) break; } freeifaddrs(ifa); return (wa.res); } struct nwamd_wlan_condition_walk_arg { nwam_condition_t condition; const char *exp_essid; const char *exp_bssid; uint_t num_connected; boolean_t res; }; static int check_wlan(const char *linkname, void *arg) { struct nwamd_wlan_condition_walk_arg *wa = arg; datalink_id_t linkid; dladm_wlan_linkattr_t attr; dladm_status_t status; char cur_essid[DLADM_STRSIZE]; char cur_bssid[DLADM_STRSIZE]; char errmsg[DLADM_STRSIZE]; if ((status = dladm_name2info(dld_handle, linkname, &linkid, NULL, NULL, NULL)) != DLADM_STATUS_OK) { nlog(LOG_DEBUG, "check_wlan: dladm_name2info() for %s " "failed: %s", linkname, dladm_status2str(status, errmsg)); return (DLADM_WALK_CONTINUE); } status = dladm_wlan_get_linkattr(dld_handle, linkid, &attr); if (status != DLADM_STATUS_OK) { nlog(LOG_DEBUG, "check_wlan: dladm_wlan_get_linkattr() for %s " "failed: %s", linkname, dladm_status2str(status, errmsg)); return (DLADM_WALK_CONTINUE); } if (attr.la_status == DLADM_WLAN_LINK_DISCONNECTED) return (DLADM_WALK_TERMINATE); wa->num_connected++; if (wa->exp_essid != NULL) { /* Is the NIC associated with the expected access point? */ (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, cur_essid); switch (wa->condition) { case NWAM_CONDITION_IS: wa->res = strcmp(cur_essid, wa->exp_essid) == 0; if (wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_IS_NOT: wa->res = strcmp(cur_essid, wa->exp_essid) != 0; if (!wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_CONTAINS: wa->res = strstr(cur_essid, wa->exp_essid) != NULL; if (wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_DOES_NOT_CONTAIN: wa->res = strstr(cur_essid, wa->exp_essid) == NULL; if (!wa->res) return (DLADM_WALK_TERMINATE); break; default: return (DLADM_WALK_TERMINATE); } return (DLADM_WALK_CONTINUE); } if (wa->exp_bssid != NULL) { /* Is the NIC associated with the expected access point? */ (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, cur_bssid); switch (wa->condition) { case NWAM_CONDITION_IS: wa->res = strcmp(cur_bssid, wa->exp_bssid) == 0; if (wa->res) return (DLADM_WALK_TERMINATE); break; case NWAM_CONDITION_IS_NOT: wa->res = strcmp(cur_bssid, wa->exp_bssid) != 0; if (!wa->res) return (DLADM_WALK_TERMINATE); break; default: return (DLADM_WALK_TERMINATE); } return (DLADM_WALK_CONTINUE); } /* * Neither an ESSID or BSSID match is required - being connected to a * WLAN is enough. */ switch (wa->condition) { case NWAM_CONDITION_IS: wa->res = B_TRUE; return (DLADM_WALK_TERMINATE); default: wa->res = B_FALSE; return (DLADM_WALK_TERMINATE); } /*NOTREACHED*/ return (DLADM_WALK_CONTINUE); } static boolean_t test_condition_wireless_essid(nwam_condition_t condition, const char *essid) { struct nwamd_wlan_condition_walk_arg wa; wa.condition = condition; wa.exp_essid = essid; wa.exp_bssid = NULL; wa.num_connected = 0; wa.res = B_FALSE; (void) dladm_walk(check_wlan, dld_handle, &wa, DATALINK_CLASS_PHYS, DL_WIFI, DLADM_OPT_ACTIVE); return (wa.num_connected > 0 && wa.res == B_TRUE); } static boolean_t test_condition_wireless_bssid(nwam_condition_t condition, const char *bssid) { struct nwamd_wlan_condition_walk_arg wa; wa.condition = condition; wa.exp_bssid = bssid; wa.exp_essid = NULL; wa.num_connected = 0; wa.res = B_FALSE; (void) dladm_walk(check_wlan, dld_handle, &wa, DATALINK_CLASS_PHYS, DL_WIFI, DLADM_OPT_ACTIVE); return (wa.num_connected > 0 && wa.res == B_TRUE); } /* * This function takes an activation mode and a string representation of a * condition and evaluates it. */ boolean_t nwamd_check_conditions(nwam_activation_mode_t activation_mode, char **condition_strings, uint_t num_conditions) { boolean_t ret = B_FALSE; nwam_condition_t condition; nwam_condition_object_type_t object_type; char *object_name; int i, j; for (i = 0; i < num_conditions; i++) { if (nwam_condition_string_to_condition(condition_strings[i], &object_type, &condition, &object_name) != NWAM_SUCCESS) { nlog(LOG_ERR, "check_conditions: invalid condition %s", condition_strings[i]); return (B_FALSE); } ret = B_FALSE; for (j = 0; j < (sizeof (condition_map) / sizeof (struct nwamd_condition_map)); j++) { if (condition_map[j].object_type == object_type) ret = condition_map[j].condition_func(condition, object_name); } free(object_name); if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ANY && ret) { return (B_TRUE); } if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ALL && !ret) { return (B_FALSE); } } if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ANY && ret) return (B_TRUE); if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ALL && ret) return (B_TRUE); return (B_FALSE); } /* * In rating activation conditions, we take the best-rated CONDITIONAL_ANY * condition, or sum all the CONDITIONAL_ALL condition ratings. This allows * us to compare between location activation conditions to pick the best. */ uint64_t nwamd_rate_conditions(nwam_activation_mode_t activation_mode, char **conditions, uint_t num_conditions) { nwam_condition_t condition; nwam_condition_object_type_t object_type; char *object_name; int i; uint64_t rating = 0, total_rating = 0; for (i = 0; i < num_conditions; i++) { object_name = NULL; if (nwam_condition_string_to_condition(conditions[i], &object_type, &condition, &object_name) != NWAM_SUCCESS || nwam_condition_rate(object_type, condition, &rating) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_rate_conditions: could not rate " "condition"); free(object_name); return (0); } free(object_name); if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ANY) { if (rating > total_rating) total_rating = rating; } else if (activation_mode == NWAM_ACTIVATION_MODE_CONDITIONAL_ALL) { total_rating += rating; } } return (total_rating); } /* * Different from nwamd_triggered_check_all_conditions() in that this * function enqueues a timed check event. */ void nwamd_set_timed_check_all_conditions(void) { nwamd_event_t check_event = nwamd_event_init (NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); if (check_event != NULL) { /* Add another timed event to recheck conditions */ nwamd_event_enqueue_timed(check_event, condition_check_interval > CONDITION_CHECK_INTERVAL_MIN ? condition_check_interval : CONDITION_CHECK_INTERVAL_MIN); } } /* * Does not enqueue another check event. */ void nwamd_check_all_conditions(void) { nwamd_enm_check_conditions(); nwamd_loc_check_conditions(); } void nwamd_create_timed_condition_check_event(void) { nwamd_event_t check_event = nwamd_event_init (NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); if (check_event != NULL) nwamd_event_enqueue(check_event); } void nwamd_create_triggered_condition_check_event(uint32_t when) { nwamd_event_t check_event; if (!nwamd_event_enqueued(NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, NULL)) { check_event = nwamd_event_init (NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); if (check_event != NULL) nwamd_event_enqueue_timed(check_event, when); } } /* * 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 _CONDITIONS_H #define _CONDITIONS_H #include #define CONDITION_CHECK_INTERVAL_DEFAULT 120 #define CONDITION_CHECK_INTERVAL_MIN 30 extern uint64_t condition_check_interval; /* Common condition check function */ extern boolean_t nwamd_check_conditions(nwam_activation_mode_t, char **, uint_t); /* Rate condition (used to pick best location condition) */ extern uint64_t nwamd_rate_conditions(nwam_activation_mode_t, char **, uint_t); /* Check activation conditions */ extern void nwamd_set_timed_check_all_conditions(void); extern void nwamd_check_all_conditions(void); /* Create condition check events */ extern void nwamd_create_timed_condition_check_event(void); extern void nwamd_create_triggered_condition_check_event(uint32_t); #endif /* _CONDITIONS_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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # create loc Automatic set activation-mode=system set nameservices=dns set nameservices-config-file=/etc/nsswitch.dns set dns-nameservice-configsrc=dhcp end # # 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. # create loc NoNet set activation-mode=system set nameservices=files set nameservices-config-file=/etc/nsswitch.files set ipfilter-config-file=/etc/nwam/loc/NoNet/ipf.conf set ipfilter-v6-config-file=/etc/nwam/loc/NoNet/ipf6.conf end /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "events.h" #include "ncp.h" #include "ncu.h" #include "objects.h" #include "util.h" /* * dlpi_events.c - this file contains routines to retrieve * DL_NOTE_LINK_[UP|DOWN] events from the system and packages them for high * level processing. Holding a dlpi_handle to a link prevents the * associated driver unloading that can happen when IP is not plumbed, * so it is vital to ensure that the handle is open for the lifetime * of the WiFi connection. */ /* * This is a callback function executed when dlpi_recv() gets a DL_NOTE_LINK_UP. * It packages up the event for consumption by the link state machine. */ /* ARGSUSED0 */ static void nwamd_dlpi_notify(dlpi_handle_t dhp, dlpi_notifyinfo_t *info, void *arg) { nwamd_event_t ev; char *name = arg; if (info->dni_note & DL_NOTE_LINK_UP) ev = nwamd_event_init_link_state(name, B_TRUE); else ev = nwamd_event_init_link_state(name, B_FALSE); if (ev != NULL) nwamd_event_enqueue(ev); } /* * We are only intested in DL_NOTE_LINK_UP events which we've registered for * in nwamd_dlpi_add_link(). But we have to keep calling dlpi_recv() to * force the notification callback to be executed. */ static void * nwamd_dlpi_thread(void *arg) { int rc; dlpi_handle_t *dh = arg; do { rc = dlpi_recv(*dh, NULL, NULL, NULL, NULL, -1, NULL); } while (rc == DLPI_SUCCESS); nlog(LOG_ERR, "dlpi_recv failed: %s", dlpi_strerror(rc)); return (NULL); } /* * This is called when we want to start receiving notifications from state * changes on a link. */ void nwamd_dlpi_add_link(nwamd_object_t obj) { nwamd_ncu_t *ncu = obj->nwamd_object_data; nwamd_link_t *link; dlpi_notifyid_t id; int rc; nlog(LOG_DEBUG, "nwamd_dlpi_add_link: ncu %p (%s) type %d", ncu, obj->nwamd_object_name, ncu != NULL ? ncu->ncu_type : -1); assert(ncu != NULL && ncu->ncu_type == NWAM_NCU_TYPE_LINK); link = &ncu->ncu_link; /* Already running? */ if (link->nwamd_link_dlpi_thread != 0) { nlog(LOG_DEBUG, "nwamd_dlpi_add_link(%s) already running", obj->nwamd_object_name); return; } rc = dlpi_open(ncu->ncu_name, &link->nwamd_link_dhp, 0); if (rc != DLPI_SUCCESS) { nlog(LOG_ERR, "nwamd_dlpi_add_link: dlpi_open(%s) = %s", ncu->ncu_name, dlpi_strerror(rc)); return; } nwamd_set_unset_link_properties(ncu, B_TRUE); rc = dlpi_enabnotify(link->nwamd_link_dhp, DL_NOTE_LINK_UP | DL_NOTE_LINK_DOWN, nwamd_dlpi_notify, ncu->ncu_name, &id); if (rc != DLPI_SUCCESS) { nlog(LOG_ERR, "nwamd_dlpi_add_link: dlpi_enabnotify(%s) = %s", obj->nwamd_object_name, dlpi_strerror(rc)); dlpi_close(link->nwamd_link_dhp); return; } rc = pthread_create(&link->nwamd_link_dlpi_thread, NULL, nwamd_dlpi_thread, &link->nwamd_link_dhp); if (rc != 0) { nlog(LOG_ERR, "nwamd_dlpi_add_link: couldn't create " "dlpi thread for %s: %s", obj->nwamd_object_name, strerror(rc)); dlpi_close(link->nwamd_link_dhp); } } /* * This function is called when we are no longer interested in receiving * notification from state changes on a link. */ void nwamd_dlpi_delete_link(nwamd_object_t obj) { nwamd_ncu_t *ncu = obj->nwamd_object_data; nlog(LOG_DEBUG, "nwamd_dlpi_delete_link: ncu %p (%s) type %d", ncu, obj->nwamd_object_name, ncu != NULL ? ncu->ncu_type : -1); if (ncu->ncu_link.nwamd_link_dlpi_thread != 0) { (void) pthread_cancel( ncu->ncu_link.nwamd_link_dlpi_thread); (void) pthread_join(ncu->ncu_link.nwamd_link_dlpi_thread, NULL); ncu->ncu_link.nwamd_link_dlpi_thread = 0; /* Unset properties before closing */ nwamd_set_unset_link_properties(ncu, B_FALSE); } dlpi_close(ncu->ncu_link.nwamd_link_dhp); ncu->ncu_link.nwamd_link_dhp = NULL; } /* * 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) 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 #include "conditions.h" #include "events.h" #include "ncp.h" #include "ncu.h" #include "objects.h" #include "util.h" /* * door_if.c * This file contains functions which implement the command interface to * nwam via the door NWAM_DOOR. Doors provide a LPC mechanism that allows * for threads in one process to cause code to execute in another process. * Doors also provide the ability to pass data and file descriptors. See * libdoor(3LIB) for more information. * * This file exports two functions, nwamd_door_initialize() (which sets up * the door) and nwamd_door_fini(), which removes it. * * It sets up the static routine nwamd_door_switch() to be called when a client * calls the door (via door_call(3C)). The structure nwam_request_t is * passed as data and contains data to specify the type of action requested * and any data need to meet that request. A table consisting of entries * for each door request, the associated authorization and the function to * process that request is used to handle the various requests. */ struct nwamd_door_req_entry { int ndre_type; char *ndre_auth; nwam_error_t (*ndre_fn)(nwamd_door_arg_t *, ucred_t *, struct passwd *); }; static nwam_error_t nwamd_door_req_event_register(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_event_unregister(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_wlan_scan(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_wlan_scan_results(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_wlan_select(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_wlan_set_key(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_action(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_state(nwamd_door_arg_t *, ucred_t *, struct passwd *); static nwam_error_t nwamd_door_req_priority_group(nwamd_door_arg_t *, ucred_t *, struct passwd *); /* * This table defines the set of door commands available, the required * authorizations for each command, and the function that carries out * each command. */ struct nwamd_door_req_entry door_req_table[] = { { NWAM_REQUEST_TYPE_EVENT_REGISTER, AUTOCONF_READ_AUTH, nwamd_door_req_event_register }, { NWAM_REQUEST_TYPE_EVENT_UNREGISTER, AUTOCONF_READ_AUTH, nwamd_door_req_event_unregister }, { NWAM_REQUEST_TYPE_WLAN_SCAN, AUTOCONF_WLAN_AUTH, nwamd_door_req_wlan_scan }, { NWAM_REQUEST_TYPE_WLAN_SCAN_RESULTS, AUTOCONF_READ_AUTH, nwamd_door_req_wlan_scan_results }, { NWAM_REQUEST_TYPE_WLAN_SELECT, AUTOCONF_WLAN_AUTH, nwamd_door_req_wlan_select }, { NWAM_REQUEST_TYPE_WLAN_SET_KEY, AUTOCONF_WLAN_AUTH, nwamd_door_req_wlan_set_key }, /* Requires WRITE, SELECT or WLAN auth depending on action */ { NWAM_REQUEST_TYPE_ACTION, NULL, nwamd_door_req_action }, { NWAM_REQUEST_TYPE_STATE, AUTOCONF_READ_AUTH, nwamd_door_req_state }, { NWAM_REQUEST_TYPE_PRIORITY_GROUP, AUTOCONF_READ_AUTH, nwamd_door_req_priority_group }, }; int doorfd = -1; /* ARGSUSED */ static nwam_error_t nwamd_door_req_event_register(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { nwam_error_t err; err = nwam_event_queue_init (req->nwda_data.nwdad_register_info.nwdad_name); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_door_req_event_register: " "could not register events for %s", req->nwda_data.nwdad_register_info.nwdad_name); } return (err); } /* ARGSUSED */ static nwam_error_t nwamd_door_req_event_unregister(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { nwam_event_queue_fini(req->nwda_data.nwdad_register_info.nwdad_name); return (NWAM_SUCCESS); } /* ARGSUSED1 */ static nwam_error_t nwamd_door_req_wlan_scan(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { nlog(LOG_DEBUG, "nwamd_door_req_wlan_scan: processing WLAN scan request: " "link %s", req->nwda_data.nwdad_wlan_info.nwdad_name); return (nwamd_wlan_scan(req->nwda_data.nwdad_wlan_info.nwdad_name)); } /* ARGSUSED */ static nwam_error_t nwamd_door_req_wlan_scan_results(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { nwamd_object_t obj; nwamd_ncu_t *ncu; nwamd_link_t *link; uint_t num_wlans; nlog(LOG_DEBUG, "nwamd_door_req_wlan_scan_results: processing WLAN " "scan results request: link %s", req->nwda_data.nwdad_wlan_info.nwdad_name); obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, req->nwda_data.nwdad_wlan_info.nwdad_name); if (obj == NULL) { nlog(LOG_ERR, "nwamd_door_req_wlan_scan_results: link %s not found", req->nwda_data.nwdad_wlan_info.nwdad_name); return (NWAM_ENTITY_NOT_FOUND); } ncu = obj->nwamd_object_data; link = &ncu->ncu_link; num_wlans = link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr_num; if (num_wlans > 0) { (void) memcpy (req->nwda_data.nwdad_wlan_info.nwdad_wlans, link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr, num_wlans * sizeof (nwam_wlan_t)); } req->nwda_data.nwdad_wlan_info.nwdad_num_wlans = num_wlans; nlog(LOG_DEBUG, "nwamd_door_req_wlan_scan_results: returning %d scan results", num_wlans); nwamd_object_release(obj); return (NWAM_SUCCESS); } /* ARGSUSED */ static nwam_error_t nwamd_door_req_wlan_select(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { nlog(LOG_DEBUG, "nwamd_door_req_wlan_select: processing WLAN selection : " "link %s ESSID %s , BSSID %s", req->nwda_data.nwdad_wlan_info.nwdad_name, req->nwda_data.nwdad_wlan_info.nwdad_essid, req->nwda_data.nwdad_wlan_info.nwdad_bssid); return (nwamd_wlan_select (req->nwda_data.nwdad_wlan_info.nwdad_name, req->nwda_data.nwdad_wlan_info.nwdad_essid, req->nwda_data.nwdad_wlan_info.nwdad_bssid, req->nwda_data.nwdad_wlan_info.nwdad_security_mode, req->nwda_data.nwdad_wlan_info.nwdad_add_to_known_wlans)); } /* ARGSUSED */ static nwam_error_t nwamd_door_req_wlan_set_key(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { nlog(LOG_DEBUG, "nwamd_door_req_wlan_set_key: processing WLAN key input : " "link %s ESSID %s BSSID %s", req->nwda_data.nwdad_wlan_info.nwdad_name, req->nwda_data.nwdad_wlan_info.nwdad_essid, req->nwda_data.nwdad_wlan_info.nwdad_bssid); return (nwamd_wlan_set_key (req->nwda_data.nwdad_wlan_info.nwdad_name, req->nwda_data.nwdad_wlan_info.nwdad_essid, req->nwda_data.nwdad_wlan_info.nwdad_bssid, req->nwda_data.nwdad_wlan_info.nwdad_security_mode, req->nwda_data.nwdad_wlan_info.nwdad_keyslot, req->nwda_data.nwdad_wlan_info.nwdad_key)); } static nwam_error_t nwamd_door_req_action(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { char name[NWAM_MAX_NAME_LEN]; char parent[NWAM_MAX_NAME_LEN]; nwam_action_t action = req->nwda_data.nwdad_object_action.nwdad_action; nwam_object_type_t object_type = req->nwda_data.nwdad_object_action.nwdad_object_type; char *obj_type_str = (char *)nwam_object_type_to_string(object_type); nwam_error_t err; /* Check for name, parent overrun */ if (strlcpy(name, req->nwda_data.nwdad_object_action.nwdad_name, sizeof (name)) == NWAM_MAX_NAME_LEN || strlcpy(parent, req->nwda_data.nwdad_object_action.nwdad_parent, sizeof (parent)) == NWAM_MAX_NAME_LEN) return (NWAM_INVALID_ARG); /* * Check authorizations against actions. * - ENABLE/DISABLE requires SELECT auth * - ADD/DESTROY/REFRESH on Known WLANs requires WLAN auth * - ADD/DESTROY on other objects requires WRITE auth * - REFRESH on other objects requires either WRITE or SELECT auth */ if (action == NWAM_ACTION_ENABLE || action == NWAM_ACTION_DISABLE) { if (chkauthattr(AUTOCONF_SELECT_AUTH, pwd->pw_name) == 0) { nwam_record_audit_event(ucr, action == NWAM_ACTION_ENABLE ? ADT_nwam_enable : ADT_nwam_disable, name, obj_type_str, ADT_FAILURE, ADT_FAIL_VALUE_AUTH); nlog(LOG_ERR, "nwamd_door_req_action: " "need %s for %s action", AUTOCONF_SELECT_AUTH, nwam_action_to_string(action)); return (NWAM_PERMISSION_DENIED); } } else if (object_type == NWAM_OBJECT_TYPE_KNOWN_WLAN) { if (chkauthattr(AUTOCONF_WLAN_AUTH, pwd->pw_name) == 0) { nlog(LOG_ERR, "nwamd_door_req_action: " "need %s for %s action on Known WLAN", AUTOCONF_WLAN_AUTH, nwam_action_to_string(action)); return (NWAM_PERMISSION_DENIED); } } else if (action == NWAM_ACTION_ADD || action == NWAM_ACTION_DESTROY) { if (chkauthattr(AUTOCONF_WRITE_AUTH, pwd->pw_name) == 0) { nlog(LOG_ERR, "nwamd_door_req_action: " "need %s for %s action", AUTOCONF_WRITE_AUTH, nwam_action_to_string(action)); return (NWAM_PERMISSION_DENIED); } } else if (action == NWAM_ACTION_REFRESH) { if (chkauthattr(AUTOCONF_WRITE_AUTH, pwd->pw_name) == 0 && chkauthattr(AUTOCONF_SELECT_AUTH, pwd->pw_name) == 0) { nlog(LOG_ERR, "nwamd_door_req_action: " "need either %s or %s for %s action", AUTOCONF_WRITE_AUTH, AUTOCONF_SELECT_AUTH, nwam_action_to_string(action)); return (NWAM_PERMISSION_DENIED); } } else { nlog(LOG_ERR, "nwamd_door_req_action: received unknown " "action %d (%s)", action, nwam_action_to_string(action)); return (NWAM_INVALID_ARG); } switch (action) { case NWAM_ACTION_ENABLE: case NWAM_ACTION_DISABLE: nwam_record_audit_event(ucr, action == NWAM_ACTION_ENABLE ? ADT_nwam_enable : ADT_nwam_disable, name, obj_type_str, ADT_SUCCESS, ADT_SUCCESS); nlog(LOG_DEBUG, "nwamd_door_req_action: %s %s", action == NWAM_ACTION_ENABLE ? "enabling" : "disabling", name); switch (object_type) { case NWAM_OBJECT_TYPE_ENM: err = nwamd_enm_action(name, action); break; case NWAM_OBJECT_TYPE_LOC: err = nwamd_loc_action(name, action); break; case NWAM_OBJECT_TYPE_NCU: err = nwamd_ncu_action(name, parent, action); break; case NWAM_OBJECT_TYPE_NCP: if (action == NWAM_ACTION_DISABLE) { nlog(LOG_ERR, "nwamd_door_req_action: " "NCPs cannot be disabled"); err = NWAM_INVALID_ARG; } else { err = nwamd_ncp_action(name, action); } break; default: nlog(LOG_ERR, "nwamd_door_req_action: received invalid " "object type %d (%s)", object_type, nwam_object_type_to_string(object_type)); return (NWAM_INVALID_ARG); } break; case NWAM_ACTION_ADD: case NWAM_ACTION_REFRESH: /* * Called whenever an object is committed in the library. * Reread that committed object into nwamd. */ nlog(LOG_DEBUG, "door_switch: refreshing %s", name); switch (object_type) { case NWAM_OBJECT_TYPE_ENM: err = nwamd_enm_action(name, action); break; case NWAM_OBJECT_TYPE_LOC: err = nwamd_loc_action(name, action); break; case NWAM_OBJECT_TYPE_KNOWN_WLAN: err = nwamd_known_wlan_action(name, action); break; case NWAM_OBJECT_TYPE_NCU: err = nwamd_ncu_action(name, parent, action); break; case NWAM_OBJECT_TYPE_NCP: err = nwamd_ncp_action(name, action); break; default: nlog(LOG_ERR, "nwamd_door_req_action: received invalid " "object type %d (%s)", object_type, nwam_object_type_to_string(object_type)); err = NWAM_INVALID_ARG; break; } break; case NWAM_ACTION_DESTROY: /* Object was destroyed, remove from nwamd */ nlog(LOG_DEBUG, "door_switch: removing %s", name); switch (object_type) { case NWAM_OBJECT_TYPE_ENM: err = nwamd_enm_action(name, NWAM_ACTION_DESTROY); break; case NWAM_OBJECT_TYPE_LOC: err = nwamd_loc_action(name, NWAM_ACTION_DESTROY); break; case NWAM_OBJECT_TYPE_KNOWN_WLAN: err = nwamd_known_wlan_action(name, NWAM_ACTION_DESTROY); break; case NWAM_OBJECT_TYPE_NCU: err = nwamd_ncu_action(name, parent, NWAM_ACTION_DESTROY); break; case NWAM_OBJECT_TYPE_NCP: (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(name, active_ncp) == 0) { nlog(LOG_ERR, "nwamd_door_req_action: %s is " "active, cannot destroy", parent); err = NWAM_ENTITY_IN_USE; } else { err = nwamd_ncp_action(name, NWAM_ACTION_DESTROY); } (void) pthread_mutex_unlock(&active_ncp_mutex); break; default: nlog(LOG_ERR, "nwamd_door_req_action: received invalid " "object type %d (%s)", object_type, nwam_object_type_to_string(object_type)); err = NWAM_INVALID_ARG; break; } break; default: nlog(LOG_ERR, "nwamd_door_req_action: received unknown " "action %d (%s)", action, nwam_action_to_string(action)); err = NWAM_INVALID_ARG; break; } if (err == NWAM_SUCCESS) { /* * At this point, we've successfully carried out an action. * Configuration may have changed, so we need to recheck * conditions, however we want to avoid a flurry of condition * check events, so we enqueue a triggered condition check * if none is due in the next few seconds. */ nwamd_create_triggered_condition_check_event(NEXT_FEW_SECONDS); } else { nlog(LOG_ERR, "nwamd_door_req_action: could not carry out " "%s action on %s: %s", nwam_action_to_string(action), name, nwam_strerror(err)); } return (err); } /* ARGSUSED */ static nwam_error_t nwamd_door_req_state(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { char name[NWAM_MAX_NAME_LEN]; nwamd_object_t obj; nwam_object_type_t object_type = req->nwda_data.nwdad_object_state.nwdad_object_type; boolean_t is_active = B_FALSE; /* Check for name, parent overrun */ if (strlcpy(name, req->nwda_data.nwdad_object_state.nwdad_name, sizeof (name)) == NWAM_MAX_NAME_LEN) return (NWAM_INVALID_ARG); switch (object_type) { case NWAM_OBJECT_TYPE_NCP: (void) pthread_mutex_lock(&active_ncp_mutex); is_active = (strcmp(active_ncp, name) == 0); (void) pthread_mutex_unlock(&active_ncp_mutex); if (is_active) { req->nwda_data.nwdad_object_state.nwdad_state = NWAM_STATE_ONLINE; req->nwda_data.nwdad_object_state. nwdad_aux_state = NWAM_AUX_STATE_ACTIVE; nlog(LOG_DEBUG, "nwamd_door_req_state: NCP %s is active", name); } else { req->nwda_data.nwdad_object_state.nwdad_state = NWAM_STATE_DISABLED; req->nwda_data.nwdad_object_state. nwdad_aux_state = NWAM_AUX_STATE_MANUAL_DISABLE; nlog(LOG_DEBUG, "nwamd_door_req_state: " "NCP %s is inactive", name); } break; case NWAM_OBJECT_TYPE_LOC: case NWAM_OBJECT_TYPE_NCU: case NWAM_OBJECT_TYPE_ENM: obj = nwamd_object_find(object_type, name); if (obj == NULL) { nlog(LOG_ERR, "nwamd_door_req_state: %s %s not found", nwam_object_type_to_string(object_type), name); return (NWAM_ENTITY_NOT_FOUND); } nlog(LOG_DEBUG, "nwamd_door_req_state: %s %s is %s", nwam_object_type_to_string(object_type), name, nwam_state_to_string(obj->nwamd_object_state)); req->nwda_data.nwdad_object_state.nwdad_state = obj->nwamd_object_state; req->nwda_data.nwdad_object_state.nwdad_aux_state = obj->nwamd_object_aux_state; nwamd_object_release(obj); break; default: nlog(LOG_ERR, "nwamd_door_req_state: received invalid " "object type %d (%s)", object_type, nwam_object_type_to_string(object_type)); req->nwda_status = NWAM_REQUEST_STATUS_UNKNOWN; return (NWAM_INVALID_ARG); } return (NWAM_SUCCESS); } /* ARGSUSED */ static nwam_error_t nwamd_door_req_priority_group(nwamd_door_arg_t *req, ucred_t *ucr, struct passwd *pwd) { (void) pthread_mutex_lock(&active_ncp_mutex); nlog(LOG_DEBUG, "nwamd_door_req_priority_group: " "retrieving active priority-group: %d", current_ncu_priority_group); req->nwda_data.nwdad_priority_group_info.nwdad_priority = current_ncu_priority_group; (void) pthread_mutex_unlock(&active_ncp_mutex); return (NWAM_SUCCESS); } /* ARGSUSED */ static void nwamd_door_switch(void *cookie, char *argp, size_t arg_size, door_desc_t *dp, uint_t n_desc) { nwamd_door_arg_t *req; ucred_t *ucr = NULL; uid_t uid; struct passwd *pwd = NULL; boolean_t found = B_FALSE; int i; /* LINTED E_BAD_PTR_CAST_ALIGN */ req = (nwamd_door_arg_t *)argp; req->nwda_error = NWAM_SUCCESS; if (door_ucred(&ucr) != 0) { nlog(LOG_ERR, "nwamd_door_switch: door_ucred failed: %s", strerror(errno)); req->nwda_error = NWAM_ERROR_INTERNAL; req->nwda_status = NWAM_REQUEST_STATUS_FAILED; goto done; } uid = ucred_getruid(ucr); if ((pwd = getpwuid(uid)) == NULL) { nlog(LOG_ERR, "nwamd_door_switch: getpwuid failed: %s", strerror(errno)); endpwent(); req->nwda_error = NWAM_ERROR_INTERNAL; req->nwda_status = NWAM_REQUEST_STATUS_FAILED; goto done; } /* * Find door request entry in table, check auths and call the function * handling the request. */ for (i = 0; i < sizeof (door_req_table) / sizeof (struct nwamd_door_req_entry); i++) { if (req->nwda_type != door_req_table[i].ndre_type) continue; found = B_TRUE; if (door_req_table[i].ndre_auth != NULL && chkauthattr(door_req_table[i].ndre_auth, pwd->pw_name) == 0) { nlog(LOG_ERR, "nwamd_door_switch: need %s for request type %d", door_req_table[i].ndre_auth, req->nwda_type); req->nwda_error = NWAM_PERMISSION_DENIED; break; } req->nwda_error = door_req_table[i].ndre_fn(req, ucr, pwd); break; } if (!found) { nlog(LOG_ERR, "nwamd_door_switch: received unknown request type %d", req->nwda_type); req->nwda_status = NWAM_REQUEST_STATUS_UNKNOWN; } else { if (req->nwda_error == NWAM_SUCCESS) req->nwda_status = NWAM_REQUEST_STATUS_OK; else req->nwda_status = NWAM_REQUEST_STATUS_FAILED; } done: ucred_free(ucr); endpwent(); if (door_return((char *)req, sizeof (nwamd_door_arg_t), NULL, 0) == -1) { nlog(LOG_ERR, "door_switch: type %d door_return failed: %s", req->nwda_type, strerror(errno)); } } /* * We initialize the nwamd door here. Failure to have this happen is critical * to the daemon so we log a message and pass up notice to the caller who * will most likely abort trying to start. This routine is meant to only * be called once. */ void nwamd_door_init(void) { const int door_mode = 0644; struct stat buf; if ((doorfd = door_create(nwamd_door_switch, NULL, DOOR_NO_CANCEL | DOOR_REFUSE_DESC)) == -1) pfail("Unable to create door: %s", strerror(errno)); if (stat(NWAM_DOOR, &buf) < 0) { int nwam_door_fd; if ((nwam_door_fd = creat(NWAM_DOOR, door_mode)) < 0) { int err = errno; (void) door_revoke(doorfd); doorfd = -1; pfail("Couldn't create door: %s", strerror(err)); } (void) close(nwam_door_fd); } else { if (buf.st_mode != door_mode) { if (chmod(NWAM_DOOR, door_mode) == -1) { nlog(LOG_ERR, "couldn't change mode of %s: %s", NWAM_DOOR, strerror(errno)); } } } /* cleanup anything hanging around from a previous invocation */ (void) fdetach(NWAM_DOOR); /* Place our door in the file system so that others can find us. */ if (fattach(doorfd, NWAM_DOOR) < 0) { int err = errno; (void) door_revoke(doorfd); doorfd = -1; pfail("Couldn't attach door: %s", strerror(err)); } } void nwamd_door_fini(void) { if (doorfd != -1) { nlog(LOG_DEBUG, "nwamd_door_fini: closing door"); (void) door_revoke(doorfd); doorfd = -1; } (void) unlink(NWAM_DOOR); } /* * 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) 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 "conditions.h" #include "events.h" #include "objects.h" #include "util.h" /* * enm.c - contains routines which handle ENM (external network modifier) * abstraction. ENMs represent scripts or services that can be activated either * manually or in response to network conditions. */ #define CTRUN "/usr/bin/ctrun" static int enm_create_init_fini_event(nwam_enm_handle_t enmh, void *data) { boolean_t *init = data; char *name; nwamd_event_t enm_event; if (nwam_enm_get_name(enmh, &name) != NWAM_SUCCESS) { nlog(LOG_ERR, "enm_init_fini: could not get enm name"); return (0); } enm_event = nwamd_event_init(*init ? NWAM_EVENT_TYPE_OBJECT_INIT : NWAM_EVENT_TYPE_OBJECT_FINI, NWAM_OBJECT_TYPE_ENM, 0, name); if (enm_event != NULL) nwamd_event_enqueue(enm_event); free(name); return (0); } /* * Walk all ENMs, creating init events for each. */ void nwamd_init_enms(void) { boolean_t init = B_TRUE; (void) nwam_walk_enms(enm_create_init_fini_event, &init, 0, NULL); } /* * Walk all ENMs, creating fini events for each. */ void nwamd_fini_enms(void) { boolean_t init = B_FALSE; (void) nwam_walk_enms(enm_create_init_fini_event, &init, 0, NULL); } static boolean_t enm_is_enabled(nwam_enm_handle_t enmh) { nwam_value_t enabledval; boolean_t enabled = B_FALSE; if (nwam_enm_get_prop_value(enmh, NWAM_ENM_PROP_ENABLED, &enabledval) != NWAM_SUCCESS) { /* It's legal for a conditional ENM to not specify "enabled" */ return (B_FALSE); } if (nwam_value_get_boolean(enabledval, &enabled) != NWAM_SUCCESS) { nlog(LOG_ERR, "enm_is_enabled: could not retrieve " "enabled value"); } nwam_value_free(enabledval); return (enabled); } static int64_t enm_get_activation_mode(nwam_enm_handle_t enmh) { uint64_t activation; int64_t ret; nwam_value_t activationval; if (nwam_enm_get_prop_value(enmh, NWAM_ENM_PROP_ACTIVATION_MODE, &activationval) != NWAM_SUCCESS) { nlog(LOG_ERR, "enm_get_activation_mode: could not retrieve " "activation mode value"); return (-1); } if (nwam_value_get_uint64(activationval, &activation) != NWAM_SUCCESS) { nlog(LOG_ERR, "enm_get_activation_mode: could not retrieve " "activation mode value"); ret = -1; } else { ret = activation; } nwam_value_free(activationval); return (ret); } static void * nwamd_enm_activate_deactivate_thread(void *arg) { char *object_name = arg; nwamd_object_t object; nwam_enm_handle_t enmh; nwam_value_t scriptval = NULL; nwam_state_t state; nwam_aux_state_t aux_state; char *script, *copy = NULL; const char **argv = NULL; boolean_t going_online, disable_succeeded = B_FALSE; int ret; object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, object_name); if (object == NULL) { nlog(LOG_ERR, "nwamd_enm_activate_deactivate_thread: " "could not find enm %s", object_name); goto done; } enmh = object->nwamd_object_handle; going_online = (object->nwamd_object_state == NWAM_STATE_OFFLINE_TO_ONLINE); /* * We're starting if current state is offline* and stopping otherwise. */ if (nwam_enm_get_prop_value(enmh, going_online ? NWAM_ENM_PROP_START : NWAM_ENM_PROP_STOP, &scriptval) != NWAM_SUCCESS || nwam_value_get_string(scriptval, &script) != NWAM_SUCCESS) { /* * If we're stopping, it's not an error for no script to * be specified. */ nlog(going_online ? LOG_ERR : LOG_DEBUG, "nwamd_enm_activate_deactivate_thread: " "no script specified for enm %s", object_name); if (going_online) { state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_METHOD_MISSING; } else { disable_succeeded = B_TRUE; } } else { char *lasts; const char **newargv; int i = 0; struct timeval now; nlog(LOG_DEBUG, "nwamd_enm_activate_deactivate_thread: " "running script %s for enm %s", script, object_name); /* * The script may take a number of arguments. We need to * create a string array consisting of the wrapper command * (ctrun), ENM script name, arguments and NULL array * terminator. Start with an array of size equal to the * string length (since the number of arguments will always * be less than this) and shrink array to the actual number * of arguments when we have parsed the string. */ if ((copy = strdup(script)) == NULL || (argv = calloc(strlen(script), sizeof (char *))) == NULL) { ret = 1; goto err; } argv[i++] = CTRUN; argv[i++] = strtok_r(copy, " ", &lasts); if (argv[1] == NULL) { ret = 1; goto err; } for (; (argv[i] = strtok_r(NULL, " ", &lasts)) != NULL; i++) {} newargv = realloc(argv, (i + 1) * sizeof (char *)); argv = newargv; /* Store the current time as the time the script began */ (void) gettimeofday(&now, NULL); object->nwamd_script_time = now; /* * Release the object so that it is not blocked while the * script is running. */ nwamd_object_release(object); ret = nwamd_start_childv(CTRUN, argv); /* * Find the object again, now that the script has finished * running. Check if this ENM was re-read during that time by * comparing the object's script time with the one from above. */ object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, object_name); if (object == NULL) { nlog(LOG_ERR, "nwamd_enm_activate_deactivate_thread: " "could not find enm %s after running script", object_name); goto done; } if (object->nwamd_script_time.tv_sec != now.tv_sec || object->nwamd_script_time.tv_usec != now.tv_usec) { nlog(LOG_INFO, "nwamd_enm_activate_deactivate_thread: " "enm %s has been refreshed, nothing to do", object_name); nwamd_object_release(object); goto done; } (void) gettimeofday(&object->nwamd_script_time, NULL); err: /* * If script execution fails and we're not destroying the * object, go to maintenance. */ if (ret != 0) { nlog(LOG_ERR, "nwamd_enm_activate_deactivate_thread: " "execution of '%s' failed for enm %s", script, object_name); if (object->nwamd_object_aux_state != NWAM_AUX_STATE_UNINITIALIZED) { state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_METHOD_FAILED; } else { state = NWAM_STATE_UNINITIALIZED; aux_state = NWAM_AUX_STATE_UNINITIALIZED; } } else { if (going_online) { state = NWAM_STATE_ONLINE; aux_state = NWAM_AUX_STATE_ACTIVE; } else { disable_succeeded = B_TRUE; } } } if (disable_succeeded) { /* * If aux state is "manual disable", we know * this was a disable request, otherwise it was * _fini request or a condition satisfaction * failure. */ switch (object->nwamd_object_aux_state) { case NWAM_AUX_STATE_MANUAL_DISABLE: state = NWAM_STATE_DISABLED; aux_state = NWAM_AUX_STATE_MANUAL_DISABLE; break; case NWAM_AUX_STATE_UNINITIALIZED: state = NWAM_STATE_UNINITIALIZED; aux_state = NWAM_AUX_STATE_UNINITIALIZED; break; default: state = NWAM_STATE_OFFLINE; aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; break; } } /* If state/aux state are uninitialized/unintialized, destroy the ENM */ if (state == NWAM_STATE_UNINITIALIZED && aux_state == NWAM_AUX_STATE_UNINITIALIZED) { object->nwamd_object_state = state; object->nwamd_object_aux_state = aux_state; (void) nwamd_object_release_and_destroy_after_preserve(object); } else { nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, object->nwamd_object_name, state, aux_state); (void) nwamd_object_release_after_preserve(object); } done: /* object_name was malloc() before this thread was created, free() it */ free(object_name); free(argv); free(copy); nwam_value_free(scriptval); return (NULL); } /* * Run start/stop method for ENM in a separate thread. The object lock is not * held across threads, so we duplicate the object name for the method * execution thread. Returns true if thread is successfully launched. */ boolean_t nwamd_enm_run_method(nwamd_object_t object) { char *name; pthread_t script; /* * Launch separate thread to wait for execution of script * to complete. Do not hold object lock across threads. */ if ((name = strdup(object->nwamd_object_name)) == NULL) { nlog(LOG_ERR, "nwamd_enm_run_method: %s: out of memory", object->nwamd_object_name); return (B_FALSE); } if (pthread_create(&script, NULL, nwamd_enm_activate_deactivate_thread, name) != 0) { nlog(LOG_ERR, "nwamd_enm_run_method: could not create " "enm script thread for %s", name); free(name); return (B_FALSE); } /* "name" will be freed by the newly-created thread. */ /* detach thread so that it doesn't become a zombie */ (void) pthread_detach(script); return (B_TRUE); } /* * Activate the ENM, either in response to an enable event or conditions * being satisfied. */ static void nwamd_enm_activate(const char *object_name) { nwamd_object_t object; nwam_value_t fmrival; char *fmri, *smf_state; int ret; nwam_enm_handle_t enmh; nwam_state_t state; nwam_aux_state_t aux_state; nwam_error_t err; boolean_t ran_method = B_FALSE; object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, object_name); if (object == NULL) { nlog(LOG_ERR, "nwamd_enm_activate: could not find enm %s", object_name); return; } state = object->nwamd_object_state; aux_state = object->nwamd_object_aux_state; enmh = object->nwamd_object_handle; nlog(LOG_DEBUG, "nwamd_enm_activate: activating enm %s", object->nwamd_object_name); err = nwam_enm_get_prop_value(enmh, NWAM_ENM_PROP_FMRI, &fmrival); switch (err) { case NWAM_SUCCESS: if (nwam_value_get_string(fmrival, &fmri) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_enm_activate: could not retrieve " "fmri string for enm %s", object->nwamd_object_name); nwam_value_free(fmrival); state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_INVALID_CONFIG; break; } if ((smf_state = smf_get_state(fmri)) == NULL) { nlog(LOG_ERR, "nwamd_enm_activate: invalid fmri %s " "for enm %s", fmri, object->nwamd_object_name); nwam_value_free(fmrival); state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_INVALID_CONFIG; break; } nlog(LOG_DEBUG, "nwamd_enm_activate: activating %s for enm %s", fmri, object->nwamd_object_name); if (strcmp(smf_state, SCF_STATE_STRING_ONLINE) == 0) ret = smf_restart_instance(fmri); else if (strcmp(smf_state, SCF_STATE_STRING_OFFLINE) == 0) ret = smf_restart_instance(fmri); else if (strcmp(smf_state, SCF_STATE_STRING_DISABLED) == 0) ret = smf_enable_instance(fmri, SMF_TEMPORARY); else ret = smf_restore_instance(fmri); if (ret == 0) { state = NWAM_STATE_ONLINE; aux_state = NWAM_AUX_STATE_ACTIVE; } else { nlog(LOG_ERR, "nwamd_enm_activate: failed to enable " "fmri %s for enm %s", fmri, object->nwamd_object_name); state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_METHOD_FAILED; } free(smf_state); nwam_value_free(fmrival); break; default: /* * Must be a method-based ENM with start (and stop) script(s). */ if (!nwamd_enm_run_method(object)) { /* Could not launch method execution thread */ state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_METHOD_FAILED; } else { ran_method = B_TRUE; } break; } if (state != object->nwamd_object_state || aux_state != object->nwamd_object_aux_state) { nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, object->nwamd_object_name, state, aux_state); } /* * If the method thread was created, we drop the lock to the ENM * object without decreasing the reference count, ensuring it will not * be destroyed until method execution has completed. */ if (ran_method) { nwamd_object_release_and_preserve(object); } else { nwamd_object_release(object); } } /* Deactivates the ENM. */ static void nwamd_enm_deactivate(const char *object_name) { nwamd_object_t object; nwam_enm_handle_t enmh; nwam_value_t fmrival; char *fmri, *smf_state; int ret; nwam_state_t state; nwam_aux_state_t aux_state; boolean_t destroying = B_FALSE; object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, object_name); if (object == NULL) { nlog(LOG_ERR, "nwamd_enm_deactivate: could not find enm %s", object_name); return; } state = object->nwamd_object_state; aux_state = object->nwamd_object_aux_state; enmh = object->nwamd_object_handle; state = object->nwamd_object_state; /* If destroying, we don't care about method failure/config err */ destroying = (aux_state == NWAM_AUX_STATE_UNINITIALIZED); nlog(LOG_DEBUG, "nwamd_enm_deactivate: deactivating enm %s", object->nwamd_object_name); if (nwam_enm_get_prop_value(enmh, NWAM_ENM_PROP_FMRI, &fmrival) != NWAM_SUCCESS) { /* * Must be a method-based ENM with start (and stop) script(s). * Script execution thread will take care of the rest. * If the method thread was created, we drop the lock to the ENM * object without decreasing the reference count, ensuring it * will not be destroyed until method execution has completed. */ if (nwamd_enm_run_method(object)) { nwamd_object_release_and_preserve(object); return; } /* Could not launch method execution thread */ if (!destroying) { state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_METHOD_FAILED; } } else { if (nwam_value_get_string(fmrival, &fmri) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_enm_deactivate: could not " "retrieve fmri string for enm %s", object->nwamd_object_name); if (!destroying) { state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_INVALID_CONFIG; } } else { if ((smf_state = smf_get_state(fmri)) == NULL) { nlog(LOG_ERR, "nwamd_enm_deactivate: invalid " "fmri %s for enm %s", fmri, object->nwamd_object_name); nwam_value_free(fmrival); if (!destroying) { state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_INVALID_CONFIG; } goto done; } free(smf_state); nlog(LOG_DEBUG, "nwamd_enm_deactivate: deactivating %s " "for enm %s", fmri, object->nwamd_object_name); ret = smf_disable_instance(fmri, SMF_TEMPORARY); if (ret != 0) { nlog(LOG_ERR, "nwamd_enm_deactivate: " "smf_disable_instance(%s) failed for " "enm %s: %s", fmri, object->nwamd_object_name, scf_strerror(scf_error())); if (!destroying) { state = NWAM_STATE_MAINTENANCE; aux_state = NWAM_AUX_STATE_METHOD_FAILED; } } } nwam_value_free(fmrival); } done: if (state == object->nwamd_object_state && aux_state == object->nwamd_object_aux_state) { /* * If aux state is "manual disable", we know * this was a disable request, otherwise it was * a _fini request or a condition satisfaction * failure. */ switch (object->nwamd_object_aux_state) { case NWAM_AUX_STATE_MANUAL_DISABLE: state = NWAM_STATE_DISABLED; aux_state = NWAM_AUX_STATE_MANUAL_DISABLE; break; case NWAM_AUX_STATE_UNINITIALIZED: state = NWAM_STATE_UNINITIALIZED; aux_state = NWAM_AUX_STATE_UNINITIALIZED; break; default: state = NWAM_STATE_OFFLINE; aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; break; } } /* Only change state if we aren't destroying the ENM */ if (!destroying && (state != object->nwamd_object_state || aux_state != object->nwamd_object_aux_state)) { nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, object->nwamd_object_name, state, aux_state); } /* If state/aux state are uninitialized/unintialized, destroy the ENM */ if (state == NWAM_STATE_UNINITIALIZED && aux_state == NWAM_AUX_STATE_UNINITIALIZED) { (void) nwamd_object_release_and_destroy(object); } else { (void) nwamd_object_release(object); } } /* * Determine whether an ENM should be (de)activated. */ /* ARGSUSED1 */ static int nwamd_enm_check(nwamd_object_t object, void *data) { nwam_enm_handle_t enmh; nwam_value_t conditionval; int64_t eactivation; boolean_t enabled, satisfied; char **conditions; nwam_state_t state; uint_t nelem; state = object->nwamd_object_state; enmh = object->nwamd_object_handle; eactivation = enm_get_activation_mode(enmh); if (eactivation == -1) return (0); switch (eactivation) { case NWAM_ACTIVATION_MODE_MANUAL: enabled = enm_is_enabled(enmh); if (enabled) { nlog(LOG_DEBUG, "nwamd_enm_check: %s is enabled", object->nwamd_object_name); switch (state) { case NWAM_STATE_ONLINE: case NWAM_STATE_MAINTENANCE: /* Do nothing */ break; default: if (nwamd_enm_action(object->nwamd_object_name, NWAM_ACTION_ENABLE) != 0) { nlog(LOG_ERR, "nwamd_enm_check: " "enable failed for enm %s", object->nwamd_object_name); } break; } } else { nlog(LOG_DEBUG, "nwamd_enm_check: %s is disabled", object->nwamd_object_name); switch (state) { case NWAM_STATE_ONLINE: if (nwamd_enm_action(object->nwamd_object_name, NWAM_ACTION_DISABLE) != 0) { nlog(LOG_ERR, "nwamd_enm_check: " "disable failed for enm %s", object->nwamd_object_name); } break; case NWAM_STATE_MAINTENANCE: /* Do nothing */ break; case NWAM_STATE_DISABLED: /* Do nothing */ break; default: nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, object->nwamd_object_name, NWAM_STATE_DISABLED, NWAM_AUX_STATE_MANUAL_DISABLE); break; } } break; case NWAM_ACTIVATION_MODE_CONDITIONAL_ANY: case NWAM_ACTIVATION_MODE_CONDITIONAL_ALL: if (nwam_enm_get_prop_value(enmh, NWAM_ENM_PROP_CONDITIONS, &conditionval) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_enm_check: could not retrieve " "condition value"); break; } if (nwam_value_get_string_array(conditionval, &conditions, &nelem) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_enm_check: could not retrieve " "condition value"); nwam_value_free(conditionval); break; } satisfied = nwamd_check_conditions((uint64_t)eactivation, conditions, nelem); nlog(LOG_DEBUG, "nwamd_enm_check: conditions for enm %s " "%s satisfied", object->nwamd_object_name, satisfied ? "is" : "is not"); if (state != NWAM_STATE_ONLINE && satisfied) { nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, object->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_METHOD_RUNNING); } if (state == NWAM_STATE_ONLINE && !satisfied) { nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, object->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } nwam_value_free(conditionval); break; } return (0); } void nwamd_enm_check_conditions(void) { (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_ENM, nwamd_enm_check, NULL); } int nwamd_enm_action(const char *enm, nwam_action_t action) { nwamd_event_t event = nwamd_event_init_object_action (NWAM_OBJECT_TYPE_ENM, enm, NULL, action); if (event == NULL) return (1); nwamd_event_enqueue(event); return (0); } /* * Event handling functions. */ /* Handle ENM initialization/refresh event */ void nwamd_enm_handle_init_event(nwamd_event_t event) { nwamd_object_t object; nwam_enm_handle_t enmh; nwam_error_t err; boolean_t manual_disabled = B_FALSE; if ((err = nwam_enm_read(event->event_object, 0, &enmh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_enm_handle_init_event: could not " "read object '%s': %s", event->event_object, nwam_strerror(err)); nwamd_event_do_not_send(event); return; } if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, event->event_object)) != NULL) { nwam_enm_free(object->nwamd_object_handle); object->nwamd_object_handle = enmh; } else { object = nwamd_object_init(NWAM_OBJECT_TYPE_ENM, event->event_object, enmh, NULL); object->nwamd_object_state = NWAM_STATE_OFFLINE; object->nwamd_object_aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; } /* (Re)set script time to now as the object has just been (re)read */ (void) gettimeofday(&object->nwamd_script_time, NULL); manual_disabled = (enm_get_activation_mode(enmh) == NWAM_ACTIVATION_MODE_MANUAL && !enm_is_enabled(enmh)); /* * If this ENM is ONLINE, and not manual and disabled (since in * that case it was online but we've just set enabled = false as part * of a disable action), then it is still active but refreshing. * Change states to re-activate itself. */ if (!manual_disabled && object->nwamd_object_state == NWAM_STATE_ONLINE) { nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_METHOD_RUNNING); } nwamd_object_release(object); } /* Handle ENM finish event */ void nwamd_enm_handle_fini_event(nwamd_event_t event) { nwamd_event_t state_event; nlog(LOG_DEBUG, "nwamd_enm_handle_fini_event(%s)", event->event_object); /* * Simulate a state event so that the state machine can correctly * deactivate the ENM and free up the handle. */ state_event = nwamd_event_init_object_state(NWAM_OBJECT_TYPE_ENM, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_UNINITIALIZED); if (state_event == NULL) { nwamd_event_do_not_send(event); return; } nwamd_enm_handle_state_event(state_event); nwamd_event_fini(state_event); /* * Do not free the handle and object. * nwamd_enm_activate_deactivate_thread() and * nwamd_enm_deactivate() does this after running the stop script * and disabling the FMRI respectively. */ } void nwamd_enm_handle_action_event(nwamd_event_t event) { nwamd_object_t object; switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) { case NWAM_ACTION_ENABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_enm_handle_action_event: " "could not find enm %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_ONLINE) { nlog(LOG_DEBUG, "nwamd_enm_handle_action_event: " "enm %s already online, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_METHOD_RUNNING); break; case NWAM_ACTION_DISABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_enm_handle_action_event: " "could not find enm %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_DISABLED) { nlog(LOG_DEBUG, "nwamd_enm_handle_action_event: " "enm %s already disabled, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_ENM, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_MANUAL_DISABLE); break; case NWAM_ACTION_ADD: case NWAM_ACTION_REFRESH: nwamd_enm_handle_init_event(event); break; case NWAM_ACTION_DESTROY: nwamd_enm_handle_fini_event(event); break; default: nlog(LOG_INFO, "nwam_enm_handle_action_event: " "unexpected action"); nwamd_event_do_not_send(event); break; } } void nwamd_enm_handle_state_event(nwamd_event_t event) { nwamd_object_t object; nwam_state_t new_state; nwam_aux_state_t new_aux_state; if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_ENM, event->event_object)) == NULL) { nlog(LOG_INFO, "nwamd_enm_handle_state_event: " "state event for nonexistent enm %s", event->event_object); nwamd_event_do_not_send(event); return; } new_state = event->event_msg->nwe_data.nwe_object_state.nwe_state; new_aux_state = event->event_msg->nwe_data.nwe_object_state.nwe_aux_state; if (new_state == object->nwamd_object_state && new_aux_state == object->nwamd_object_aux_state) { nlog(LOG_DEBUG, "nwamd_enm_handle_state_event: " "enm %s already in state (%s , %s)", object->nwamd_object_name, nwam_state_to_string(new_state), nwam_aux_state_to_string(new_aux_state)); nwamd_object_release(object); return; } object->nwamd_object_state = new_state; object->nwamd_object_aux_state = new_aux_state; nlog(LOG_DEBUG, "nwamd_enm_handle_state_event: changing state for enm " "%s to (%s , %s)", object->nwamd_object_name, nwam_state_to_string(object->nwamd_object_state), nwam_aux_state_to_string(object->nwamd_object_aux_state)); nwamd_object_release(object); /* * State machine for ENMs. */ switch (new_state) { case NWAM_STATE_OFFLINE_TO_ONLINE: nwamd_enm_activate(event->event_object); break; case NWAM_STATE_ONLINE_TO_OFFLINE: nwamd_enm_deactivate(event->event_object); break; case NWAM_STATE_DISABLED: case NWAM_STATE_OFFLINE: case NWAM_STATE_UNINITIALIZED: case NWAM_STATE_MAINTENANCE: case NWAM_STATE_DEGRADED: default: /* do nothing */ break; } } /* * 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) 2007, 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "conditions.h" #include "events.h" #include "objects.h" #include "util.h" /* * events.c - contains routines which create/destroy event sources, * handle the event queue and process events from that queue. */ /* Add new event sources here. */ struct nwamd_event_source { char *name; void (*events_init)(void); void (*events_fini)(void); } event_sources[] = { { "routing_events", nwamd_routing_events_init, nwamd_routing_events_fini }, { "sysevent_events", nwamd_sysevent_events_init, nwamd_sysevent_events_fini }, }; /* Counter for event ids */ static uint64_t event_id_counter = 0; static uu_list_pool_t *event_pool = NULL; static uu_list_t *event_queue = NULL; static pthread_mutex_t event_queue_mutex = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t event_queue_cond = PTHREAD_COND_INITIALIZER; static int nwamd_event_compare(const void *, const void *, void *); static const char * nwamd_event_name(int event_type) { if (event_type <= NWAM_EVENT_MAX) return (nwam_event_type_to_string(event_type)); switch (event_type) { case NWAM_EVENT_TYPE_OBJECT_INIT: return ("OBJECT_INIT"); case NWAM_EVENT_TYPE_OBJECT_FINI: return ("OBJECT_FINI"); case NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS: return ("TIMED_CHECK_CONDITIONS"); case NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS: return ("TRIGGERED_CHECK_CONDITIONS"); case NWAM_EVENT_TYPE_NCU_CHECK: return ("NCU_CHECK"); case NWAM_EVENT_TYPE_TIMER: return ("TIMER"); case NWAM_EVENT_TYPE_UPGRADE: return ("UPGRADE"); case NWAM_EVENT_TYPE_PERIODIC_SCAN: return ("PERIODIC_SCAN"); case NWAM_EVENT_TYPE_QUEUE_QUIET: return ("QUEUE_QUIET"); default: return ("N/A"); } } void nwamd_event_sources_init(void) { int i; /* * Now we can safely initialize event sources. */ for (i = 0; i < sizeof (event_sources) / sizeof (struct nwamd_event_source); i++) { if (event_sources[i].events_init != NULL) event_sources[i].events_init(); } } void nwamd_event_sources_fini(void) { int i; for (i = 0; i < sizeof (event_sources) / sizeof (struct nwamd_event_source); i++) { if (event_sources[i].events_init != NULL) event_sources[i].events_fini(); } } /* * Comparison function for events, passed in as callback to * uu_list_pool_create(). Compare by time, so that timer * event queue can be sorted by nearest time to present. */ /* ARGSUSED */ static int nwamd_event_compare(const void *l_arg, const void *r_arg, void *private) { nwamd_event_t l = (nwamd_event_t)l_arg; nwamd_event_t r = (nwamd_event_t)r_arg; int rv; rv = l->event_time.tv_sec - r->event_time.tv_sec; if (rv == 0) rv = l->event_time.tv_nsec - r->event_time.tv_nsec; return (rv); } void nwamd_event_queue_init(void) { event_pool = uu_list_pool_create("event_queue_pool", sizeof (struct nwamd_event), offsetof(struct nwamd_event, event_node), nwamd_event_compare, UU_LIST_POOL_DEBUG); if (event_pool == NULL) pfail("uu_list_pool_create failed with error %d", uu_error()); event_queue = uu_list_create(event_pool, NULL, UU_LIST_SORTED); if (event_queue == NULL) pfail("uu_list_create failed with error %d", uu_error()); } void nwamd_event_queue_fini(void) { void *cookie = NULL; nwamd_event_t event; while ((event = uu_list_teardown(event_queue, &cookie)) != NULL) nwamd_event_fini(event); uu_list_destroy(event_queue); if (event_pool != NULL) uu_list_pool_destroy(event_pool); } nwamd_event_t nwamd_event_init(int32_t type, nwam_object_type_t object_type, size_t size, const char *object_name) { nwamd_event_t event; event = calloc(1, sizeof (struct nwamd_event)); if (event == NULL) { nlog(LOG_ERR, "nwamd_event_init: could not create %s event for " "object %s", nwamd_event_name(type), object_name != NULL ? object_name : ""); return (NULL); } /* Is this an externally-visible event? */ if (type <= NWAM_EVENT_MAX) { event->event_send = B_TRUE; event->event_msg = calloc(1, sizeof (struct nwam_event) + size); if (event->event_msg == NULL) { nlog(LOG_ERR, "nwamd_event_init: could not create %s event", nwamd_event_name(type)); free(event); return (NULL); } event->event_msg->nwe_type = type; event->event_msg->nwe_size = sizeof (struct nwam_event) + size; } else { event->event_send = B_FALSE; event->event_msg = NULL; } event->event_type = type; if (object_name != NULL) { (void) strlcpy(event->event_object, object_name, NWAM_MAX_NAME_LEN); event->event_object_type = object_type; } else { event->event_object[0] = '\0'; } /* Set event id */ event->event_id = atomic_add_64_nv(&event_id_counter, 1); (void) clock_gettime(CLOCK_REALTIME, &event->event_time); return (event); } void nwamd_event_do_not_send(nwamd_event_t event) { nlog(LOG_DEBUG, "nwamd_event_do_not_send: cancelling delivery of " "event %s for object %s", nwamd_event_name(event->event_type), event->event_object[0] != '\0' ? event->event_object : ""); event->event_send = B_FALSE; } void nwamd_event_fini(nwamd_event_t event) { if (event != NULL) { free(event->event_msg); free(event); } } nwamd_event_t nwamd_event_init_object_action(nwam_object_type_t object_type, const char *object_name, const char *parent_name, nwam_action_t object_action) { nwamd_event_t event; event = nwamd_event_init(NWAM_EVENT_TYPE_OBJECT_ACTION, object_type, 0, object_name); if (event == NULL) return (NULL); event->event_msg->nwe_data.nwe_object_action.nwe_action = object_action; event->event_msg->nwe_data.nwe_object_action.nwe_object_type = object_type; (void) strlcpy(event->event_msg->nwe_data.nwe_object_action.nwe_name, object_name, sizeof (event->event_msg->nwe_data.nwe_object_action.nwe_name)); if (parent_name == NULL) { event->event_msg->nwe_data.nwe_object_action.nwe_parent[0] = '\0'; return (event); } (void) strlcpy (event->event_msg->nwe_data.nwe_object_action.nwe_parent, parent_name, sizeof (event->event_msg->nwe_data.nwe_object_action.nwe_parent)); return (event); } nwamd_event_t nwamd_event_init_object_state(nwam_object_type_t object_type, const char *object_name, nwam_state_t state, nwam_aux_state_t aux_state) { nwamd_event_t event; event = nwamd_event_init(NWAM_EVENT_TYPE_OBJECT_STATE, object_type, 0, object_name); if (event == NULL) return (NULL); event->event_msg->nwe_data.nwe_object_state.nwe_state = state; event->event_msg->nwe_data.nwe_object_state.nwe_aux_state = aux_state; event->event_msg->nwe_data.nwe_object_state.nwe_object_type = object_type; (void) strlcpy(event->event_msg->nwe_data.nwe_object_state.nwe_name, object_name, sizeof (event->event_msg->nwe_data.nwe_object_state.nwe_name)); return (event); } nwamd_event_t nwamd_event_init_priority_group_change(int64_t priority) { nwamd_event_t event; event = nwamd_event_init(NWAM_EVENT_TYPE_PRIORITY_GROUP, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); if (event == NULL) return (NULL); event->event_msg->nwe_data.nwe_priority_group_info.nwe_priority = priority; return (event); } nwamd_event_t nwamd_event_init_link_action(const char *name, nwam_action_t link_action) { nwamd_event_t event; nwam_error_t err; char *object_name; if ((err = nwam_ncu_name_to_typed_name(name, NWAM_NCU_TYPE_LINK, &object_name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_event_init_link_action: " "nwam_ncu_name_to_typed_name: %s", nwam_strerror(err)); return (NULL); } event = nwamd_event_init(NWAM_EVENT_TYPE_LINK_ACTION, NWAM_OBJECT_TYPE_NCU, 0, object_name); free(object_name); if (event == NULL) return (NULL); (void) strlcpy(event->event_msg->nwe_data.nwe_link_action.nwe_name, name, sizeof (event->event_msg->nwe_data.nwe_link_action.nwe_name)); event->event_msg->nwe_data.nwe_link_action.nwe_action = link_action; return (event); } nwamd_event_t nwamd_event_init_link_state(const char *name, boolean_t up) { nwamd_event_t event; nwam_error_t err; char *object_name; if ((err = nwam_ncu_name_to_typed_name(name, NWAM_NCU_TYPE_LINK, &object_name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_event_init_link_state: " "nwam_ncu_name_to_typed_name: %s", nwam_strerror(err)); return (NULL); } event = nwamd_event_init(NWAM_EVENT_TYPE_LINK_STATE, NWAM_OBJECT_TYPE_NCU, 0, object_name); free(object_name); if (event == NULL) return (NULL); (void) strlcpy(event->event_msg->nwe_data.nwe_link_state.nwe_name, name, sizeof (event->event_msg->nwe_data.nwe_link_state.nwe_name)); event->event_msg->nwe_data.nwe_link_state.nwe_link_up = up; return (event); } nwamd_event_t nwamd_event_init_if_state(const char *linkname, uint32_t flags, uint32_t addr_added, struct sockaddr *addr, struct sockaddr *netmask) { nwamd_event_t event; nwam_error_t err; char *object_name; /* linkname does not contain the lifnum */ if ((err = nwam_ncu_name_to_typed_name(linkname, NWAM_NCU_TYPE_INTERFACE, &object_name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_event_init_if_state: " "nwam_ncu_name_to_typed_name: %s", nwam_strerror(err)); return (NULL); } event = nwamd_event_init(NWAM_EVENT_TYPE_IF_STATE, NWAM_OBJECT_TYPE_NCU, 0, object_name); free(object_name); if (event == NULL) return (NULL); (void) strlcpy(event->event_msg->nwe_data.nwe_if_state.nwe_name, linkname, sizeof (event->event_msg->nwe_data.nwe_if_state.nwe_name)); event->event_msg->nwe_data.nwe_if_state.nwe_flags = flags; event->event_msg->nwe_data.nwe_if_state.nwe_addr_added = addr_added; event->event_msg->nwe_data.nwe_if_state.nwe_addr_valid = (addr != NULL); if (addr != NULL) { bcopy(addr, &(event->event_msg->nwe_data.nwe_if_state.nwe_addr), addr->sa_family == AF_INET ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)); } if (netmask != NULL) { bcopy(netmask, &(event->event_msg->nwe_data.nwe_if_state.nwe_netmask), netmask->sa_family == AF_INET ? sizeof (struct sockaddr_in) : sizeof (struct sockaddr_in6)); } return (event); } nwamd_event_t nwamd_event_init_wlan(const char *name, int32_t type, boolean_t connected, nwam_wlan_t *wlans, uint_t num_wlans) { size_t size = 0; char *object_name; nwamd_event_t event; nwam_error_t err; switch (type) { case NWAM_EVENT_TYPE_WLAN_SCAN_REPORT: case NWAM_EVENT_TYPE_WLAN_NEED_CHOICE: size = sizeof (nwam_wlan_t) * (num_wlans - 1); break; case NWAM_EVENT_TYPE_WLAN_NEED_KEY: case NWAM_EVENT_TYPE_WLAN_CONNECTION_REPORT: break; default: nlog(LOG_ERR, "nwamd_event_init_wlan: unexpected " "event type %s (%d)", nwamd_event_name(type), type); return (NULL); } if ((err = nwam_ncu_name_to_typed_name(name, NWAM_NCU_TYPE_LINK, &object_name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_event_init_wlan: " "nwam_ncu_name_to_typed_name: %s", nwam_strerror(err)); return (NULL); } event = nwamd_event_init(type, NWAM_OBJECT_TYPE_NCU, size, object_name); free(object_name); if (event == NULL) return (NULL); (void) strlcpy(event->event_msg->nwe_data.nwe_wlan_info.nwe_name, name, sizeof (event->event_msg->nwe_data.nwe_wlan_info.nwe_name)); event->event_msg->nwe_data.nwe_wlan_info.nwe_connected = connected; event->event_msg->nwe_data.nwe_wlan_info.nwe_num_wlans = num_wlans; /* copy the wlans */ (void) memcpy(event->event_msg->nwe_data.nwe_wlan_info.nwe_wlans, wlans, num_wlans * sizeof (nwam_wlan_t)); return (event); } nwamd_event_t nwamd_event_init_ncu_check(void) { return (nwamd_event_init(NWAM_EVENT_TYPE_NCU_CHECK, NWAM_OBJECT_TYPE_NCP, 0, NULL)); } nwamd_event_t nwamd_event_init_init(void) { return (nwamd_event_init(NWAM_EVENT_TYPE_INIT, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL)); } nwamd_event_t nwamd_event_init_shutdown(void) { return (nwamd_event_init(NWAM_EVENT_TYPE_SHUTDOWN, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL)); } /* * Add event to the event list. */ void nwamd_event_enqueue(nwamd_event_t event) { nwamd_event_enqueue_timed(event, 0); } /* * Schedule an event to be added to the event list for future processing. * The event will be scheduled in delta_seconds seconds mod schedule delay and * time resolution. */ void nwamd_event_enqueue_timed(nwamd_event_t event, int delta_seconds) { uu_list_index_t idx; nlog(LOG_DEBUG, "enqueueing event %lld %d (%s) for object %s in %ds", event->event_id, event->event_type, nwamd_event_name(event->event_type), event->event_object[0] != 0 ? event->event_object : "none", delta_seconds); (void) clock_gettime(CLOCK_REALTIME, &event->event_time); event->event_time.tv_sec += delta_seconds; uu_list_node_init(event, &event->event_node, event_pool); (void) pthread_mutex_lock(&event_queue_mutex); /* * Find appropriate location to insert the event based on time. */ (void) uu_list_find(event_queue, event, NULL, &idx); (void) uu_list_insert(event_queue, event, idx); (void) pthread_cond_signal(&event_queue_cond); (void) pthread_mutex_unlock(&event_queue_mutex); } /* * Is the specified event enqueued on the event (or pending event queue) * for execution in when seconds? An object may be specified also. */ boolean_t nwamd_event_enqueued(int32_t event_type, nwam_object_type_t object_type, const char *object) { nwamd_event_t event; (void) pthread_mutex_lock(&event_queue_mutex); for (event = uu_list_first(event_queue); event != NULL; event = uu_list_next(event_queue, event)) { if (event->event_type != event_type) continue; if (object_type != NWAM_OBJECT_TYPE_UNKNOWN && event->event_object_type != object_type) continue; if (object != NULL && strcmp(object, event->event_object) != 0) continue; (void) pthread_mutex_unlock(&event_queue_mutex); return (B_TRUE); } (void) pthread_mutex_unlock(&event_queue_mutex); return (B_FALSE); } /* * Is the time in the past. */ static boolean_t in_past(struct timespec t) { struct timespec now; (void) clock_gettime(CLOCK_REALTIME, &now); if (t.tv_sec < now.tv_sec) return (B_TRUE); if (t.tv_sec > now.tv_sec) return (B_FALSE); if (t.tv_nsec < now.tv_nsec) return (B_TRUE); return (B_FALSE); } /* * Remove event at head of event list for processing. This takes a number of * nanoseconds to wait. If the number is 0 then it blocks. If there is * nothing on the queue then it returns an event which says that the queue * is quiet. */ static nwamd_event_t nwamd_event_dequeue(long nsec) { nwamd_event_t event; (void) pthread_mutex_lock(&event_queue_mutex); event = uu_list_first(event_queue); if (event == NULL && nsec == 0) { do { (void) pthread_cond_wait(&event_queue_cond, &event_queue_mutex); } while ((event = uu_list_first(event_queue)) == NULL); } else { struct timespec waitcap; if (nsec != 0) { (void) clock_gettime(CLOCK_REALTIME, &waitcap); waitcap.tv_nsec += nsec; waitcap.tv_sec += NSEC_TO_SEC(waitcap.tv_nsec); waitcap.tv_nsec = NSEC_TO_FRACNSEC(waitcap.tv_nsec); } /* * Keep going as long as the first event hasn't matured and * we havn't passed our maximum wait time. */ while ((event == NULL || !in_past(event->event_time)) && (nsec == 0 || !in_past(waitcap))) { struct timespec eventwait; /* * Three cases: * no maximum waittime - just use the event * both an event and cap - take the least one * just a maximum waittime - use it */ if (nsec == 0) { eventwait = event->event_time; } else if (event != NULL) { uint64_t diff; diff = SEC_TO_NSEC(event->event_time.tv_sec - waitcap.tv_sec) + event->event_time.tv_nsec - waitcap.tv_nsec; if (diff > 0) eventwait = waitcap; else eventwait = event->event_time; } else { /* * Note that if the event is NULL then nsec is * nonzero and waitcap is valid. */ eventwait = waitcap; } (void) pthread_cond_timedwait(&event_queue_cond, &event_queue_mutex, &eventwait); event = uu_list_first(event_queue); } } /* * At this point we've met the guard contition of the while loop. * The event at the top of the queue might be mature in which case * we use it. Otherwise we hit our cap and we need to enqueue a * quiesced queue event. */ if (event != NULL && in_past(event->event_time)) { uu_list_remove(event_queue, event); uu_list_node_fini(event, &event->event_node, event_pool); } else { event = nwamd_event_init(NWAM_EVENT_TYPE_QUEUE_QUIET, NWAM_OBJECT_TYPE_UNKNOWN, 0, NULL); } if (event != NULL) nlog(LOG_DEBUG, "dequeueing event %lld of type %d (%s) for object %s", event->event_id, event->event_type, nwamd_event_name(event->event_type), event->event_object[0] != 0 ? event->event_object : "none"); (void) pthread_mutex_unlock(&event_queue_mutex); return (event); } void nwamd_event_send(nwam_event_t event_msg) { nwam_error_t err; if (shutting_down && event_msg->nwe_type != NWAM_EVENT_TYPE_SHUTDOWN) { nlog(LOG_DEBUG, "nwamd_event_send: tossing event as nwamd " "is shutting down"); return; } err = nwam_event_send(event_msg); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_event_send: nwam_event_send: %s", nwam_strerror(err)); } } /* * Run state machine for object. Method is run if * - event method is non-null * - event method is valid for current object state (determined by * ORing the current state against the set of valid states for the method). * * If these criteria are met, the method is run. */ static void nwamd_event_run_method(nwamd_event_t event) { nwamd_event_method_t *event_methods; int i; event_methods = nwamd_object_event_methods(event->event_object_type); /* If we're shutting down, only fini events are accepted for objects */ if (shutting_down && event->event_type != NWAM_EVENT_TYPE_OBJECT_FINI) { nlog(LOG_DEBUG, "nwamd_event_run_method: tossing non-fini " "event %s for object %s", nwamd_event_name(event->event_type), event->event_object); return; } for (i = 0; event_methods[i].event_type != NWAM_EVENT_TYPE_NOOP; i++) { if (event_methods[i].event_type == event->event_type && event_methods[i].event_method != NULL) { nlog(LOG_DEBUG, "(%p) %s: running method for event %s", (void *)event, event->event_object, nwamd_event_name(event->event_type)); /* run method */ event_methods[i].event_method(event); return; } } nlog(LOG_DEBUG, "(%p) %s: no matching method for event %d (%s)", (void *)event, event->event_object, event->event_type, nwamd_event_name(event->event_type)); } /* * Called when we are checking to see what should be activated. First activate * all of the manual NCUs. Then see if we can find a valid priority group. * If we can, activate it. Otherwise try all the priority groups starting * with the lowest one that makes sense. */ static void nwamd_activate_ncus(void) { int64_t prio = INVALID_PRIORITY_GROUP; boolean_t selected; nwamd_ncp_activate_manual_ncus(); selected = nwamd_ncp_check_priority_group(&prio); if (selected) { /* * Activate chosen priority group and stop anything going on in * lesser priority groups. */ nwamd_ncp_activate_priority_group(prio); nwamd_ncp_deactivate_priority_group_all(prio + 1); } else { /* * Nothing unique could be started so try them all. Once one * of them gets into a reasonable state then we will prune * everything below it (see first part of this conditional). */ int64_t oldprio = INVALID_PRIORITY_GROUP; while (nwamd_ncp_find_next_priority_group(++oldprio, &prio)) { nwamd_ncp_activate_priority_group(prio); oldprio = prio; } } } /* * Event handler thread * * The complexity in this code comes about from wanting to delay the decision * making process until after bursts of events. Keep roughly polling (waiting * for .1s) until we see the queue quiet event and then block. */ void nwamd_event_handler(void) { boolean_t got_shutdown_event = B_FALSE; boolean_t check_conditions = B_FALSE; boolean_t ncu_check = B_FALSE; int queue_quiet_time = 0; nwamd_event_t event; /* * Dequeue events and process them. In most cases, events have * an assocated object type, and we use this to retrieve * the function that will process the event. */ while (!got_shutdown_event) { event = nwamd_event_dequeue(queue_quiet_time); /* keep pulling events as long as they are close together */ queue_quiet_time = SEC_TO_NSEC(1)/10; /* * This is an event with no associated object. */ if (event->event_object[0] == '\0') { switch (event->event_type) { case NWAM_EVENT_TYPE_NOOP: case NWAM_EVENT_TYPE_INIT: /* * The only action for an INIT event * is to relay it to event listeners, * which is done below. */ break; case NWAM_EVENT_TYPE_PRIORITY_GROUP: (void) pthread_mutex_lock(&active_ncp_mutex); current_ncu_priority_group = event->event_msg->nwe_data. nwe_priority_group_info.nwe_priority; (void) pthread_mutex_unlock(&active_ncp_mutex); break; case NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS: if (!shutting_down) { nwamd_set_timed_check_all_conditions(); check_conditions = B_TRUE; } break; case NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS: if (!shutting_down) check_conditions = B_TRUE; break; case NWAM_EVENT_TYPE_NCU_CHECK: if (!shutting_down) ncu_check = B_TRUE; break; case NWAM_EVENT_TYPE_UPGRADE: if (!shutting_down) { /* * Upgrade events have no associated * object. */ nwamd_event_run_method(event); } break; case NWAM_EVENT_TYPE_SHUTDOWN: got_shutdown_event = B_TRUE; break; /* * We want to delay processing of condition and ncu * checking until after short bursts of events. So we * keep track of times we've scheduled checking and * wait for the queue to quiesce. */ case NWAM_EVENT_TYPE_QUEUE_QUIET: queue_quiet_time = 0; /* now we can block */ if (!shutting_down && check_conditions) { nwamd_check_all_conditions(); check_conditions = B_FALSE; } if (!shutting_down && ncu_check) { nwamd_activate_ncus(); ncu_check = B_FALSE; } break; default: nlog(LOG_ERR, "event %d (%s)had no object associated " "with it", event->event_type, nwamd_event_name(event->event_type)); break; } } else { /* * Event has an associated object - run event method * for that object type (if any). */ nwamd_event_run_method(event); } /* * Send associated message to listeners if event type is * externally visible. */ if (event->event_send) nwamd_event_send(event->event_msg); nwamd_event_fini(event); } /* If we get here, we got a shutdown event. */ nwamd_event_queue_fini(); nwamd_object_lists_fini(); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _EVENTS_H #define _EVENTS_H #include #include #include #include #include #include #include struct nwamd_object; typedef struct nwamd_object *nwamd_object_t; #include "ncp.h" /* Define internal-to-nwamd events here */ #define NWAM_EVENT_TYPE_OBJECT_INIT NWAM_EVENT_MAX + 1 #define NWAM_EVENT_TYPE_OBJECT_FINI NWAM_EVENT_MAX + 2 #define NWAM_EVENT_TYPE_TIMED_CHECK_CONDITIONS NWAM_EVENT_MAX + 3 #define NWAM_EVENT_TYPE_TRIGGERED_CHECK_CONDITIONS NWAM_EVENT_MAX + 4 #define NWAM_EVENT_TYPE_NCU_CHECK NWAM_EVENT_MAX + 5 #define NWAM_EVENT_TYPE_TIMER NWAM_EVENT_MAX + 6 #define NWAM_EVENT_TYPE_UPGRADE NWAM_EVENT_MAX + 7 #define NWAM_EVENT_TYPE_PERIODIC_SCAN NWAM_EVENT_MAX + 8 #define NWAM_EVENT_TYPE_QUEUE_QUIET NWAM_EVENT_MAX + 9 #define NEXT_FEW_SECONDS 5 /* * Forward definition. */ /* * Wrapper structure for libnwam event (nwam_events_msg_t), containing * event id (used to uniquely identify events on the event queue), * associated object (if any), and uu_list_node. */ typedef struct nwamd_event { int32_t event_type; uint64_t event_id; struct timespec event_time; char event_object[NWAM_MAX_NAME_LEN]; nwam_object_type_t event_object_type; uu_list_node_t event_node; boolean_t event_send; nwam_event_t event_msg; } *nwamd_event_t; typedef struct nwamd_event_method { int32_t event_type; void (*event_method)(nwamd_event_t); } nwamd_event_method_t; extern sysevent_handle_t *shp; /* Event generator init/fini code */ extern void nwamd_routing_events_init(void); extern void nwamd_routing_events_fini(void); extern void nwamd_sysevent_events_init(void); extern void nwamd_sysevent_events_fini(void); /* Event init/enqueueing */ extern void nwamd_event_queue_init(void); extern void nwamd_event_queue_fini(void); extern void nwamd_event_sources_init(void); extern void nwamd_event_sources_fini(void); extern nwamd_event_t nwamd_event_init(int32_t, nwam_object_type_t, size_t, const char *); extern void nwamd_event_do_not_send(nwamd_event_t); extern nwamd_event_t nwamd_event_init_object_action(nwam_object_type_t, const char *, const char *, nwam_action_t); extern nwamd_event_t nwamd_event_init_object_state(nwam_object_type_t, const char *, nwam_state_t, nwam_aux_state_t); extern nwamd_event_t nwamd_event_init_priority_group_change(int64_t); extern nwamd_event_t nwamd_event_init_link_action(const char *, nwam_action_t); extern nwamd_event_t nwamd_event_init_link_state(const char *, boolean_t); extern nwamd_event_t nwamd_event_init_if_state(const char *, uint32_t, uint32_t, struct sockaddr *, struct sockaddr *); extern nwamd_event_t nwamd_event_init_wlan(const char *, int32_t, boolean_t, nwam_wlan_t *, uint_t); extern nwamd_event_t nwamd_event_init_ncu_check(void); extern nwamd_event_t nwamd_event_init_init(void); extern nwamd_event_t nwamd_event_init_shutdown(void); extern void nwamd_event_enqueue(nwamd_event_t); extern void nwamd_event_enqueue_timed(nwamd_event_t, int); extern void nwamd_event_enqueue_expired_events(void); extern boolean_t nwamd_event_enqueued(int32_t, nwam_object_type_t, const char *); extern void nwamd_event_send(nwam_event_t); extern void nwamd_event_fini(nwamd_event_t); extern void nwamd_event_handler(void); #endif /* _EVENTS_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 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # Start by blocking everything. block in log all block out log all # Allow loopback traffic pass in quick on lo0 pass out quick on lo0 # Allow DHCP: in to client port, out to server port pass in quick proto udp from any to any port = 68 pass out quick proto udp from any to any port = 67 # # 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 blocking everything. block in log all block out log all # Allow loopback traffic pass in quick on lo0 pass out quick on lo0 # Allow DHCP: in to client port, out to server port pass in quick proto udp from any to any port = 546 pass out quick proto udp from any to any port = 547 # Allow ICMP for IPv6 for Neighbor advertisements pass in quick proto ipv6-icmp from any to any pass out quick proto ipv6-icmp from any to any /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include "events.h" #include "known_wlans.h" #include "ncu.h" #include "objects.h" #include "util.h" /* * known_wlans.c - contains routines which handle the known WLAN abstraction. */ #define KNOWN_WIFI_NETS_FILE "/etc/nwam/known_wifi_nets" /* enum for parsing each line of /etc/nwam/known_wifi_nets */ typedef enum { ESSID = 0, BSSID, MAX_FIELDS } known_wifi_nets_fields_t; /* Structure for one BSSID */ typedef struct bssid { struct qelem bssid_links; char *bssid; } bssid_t; /* Structure for an ESSID and its BSSIDs */ typedef struct kw { struct qelem kw_links; char kw_essid[NWAM_MAX_NAME_LEN]; uint32_t kw_num_bssids; struct qelem kw_bssids; } kw_t; /* Holds the linked-list of ESSIDs to make Known WLANs out of */ static struct qelem kw_list; /* Used in walking secobjs looking for an ESSID prefix match. */ struct nwamd_secobj_arg { char nsa_essid_prefix[DLADM_WLAN_MAX_KEYNAME_LEN]; char nsa_keyname[DLADM_WLAN_MAX_KEYNAME_LEN]; dladm_wlan_key_t *nsa_key; uint64_t nsa_secmode; }; static void kw_list_init(void) { kw_list.q_forw = kw_list.q_back = &kw_list; } static void kw_list_free(void) { kw_t *kw; bssid_t *b; while (kw_list.q_forw != &kw_list) { kw = (kw_t *)kw_list.q_forw; /* free kw_bssids */ while (kw->kw_bssids.q_forw != &kw->kw_bssids) { b = (bssid_t *)kw->kw_bssids.q_forw; remque(&b->bssid_links); free(b->bssid); free(b); } remque(&kw->kw_links); free(kw); } } /* Returns the entry in kw_list for the given ESSID. NULL if non-existent */ static kw_t * kw_lookup(const char *essid) { kw_t *kw; if (essid == NULL) return (NULL); for (kw = (kw_t *)kw_list.q_forw; kw != (kw_t *)&kw_list; kw = (kw_t *)kw->kw_links.q_forw) { if (strcmp(essid, kw->kw_essid) == 0) return (kw); } return (NULL); } /* Adds an ESSID/BSSID combination to kw_list. Returns B_TRUE on success. */ static boolean_t kw_add(const char *essid, const char *bssid) { kw_t *kw; bssid_t *b; if ((b = calloc(1, sizeof (bssid_t))) == NULL) { nlog(LOG_ERR, "kw_add: cannot allocate for bssid_t: %m"); return (B_FALSE); } if ((kw = calloc(1, sizeof (kw_t))) == NULL) { nlog(LOG_ERR, "kw_add: cannot allocate for kw_t: %m"); free(b); return (B_FALSE); } kw->kw_bssids.q_forw = kw->kw_bssids.q_back = &kw->kw_bssids; b->bssid = strdup(bssid); (void) strlcpy(kw->kw_essid, essid, sizeof (kw->kw_essid)); kw->kw_num_bssids = 1; insque(&b->bssid_links, kw->kw_bssids.q_back); insque(&kw->kw_links, kw_list.q_back); nlog(LOG_DEBUG, "kw_add: added Known WLAN %s, BSSID %s", essid, bssid); return (B_TRUE); } /* * Add the BSSID to the given kw. Since /etc/nwam/known_wifi_nets is * populated such that the wifi networks visited later are towards the end * of the file, remove the give kw from its current position and append it * to the end of kw_list. This ensures that kw_list is in the reverse * order of visited wifi networks. Returns B_TRUE on success. */ static boolean_t kw_update(kw_t *kw, const char *bssid) { bssid_t *b; if ((b = calloc(1, sizeof (bssid_t))) == NULL) { nlog(LOG_ERR, "kw_update: cannot allocate for bssid_t: %m"); return (B_FALSE); } b->bssid = strdup(bssid); insque(&b->bssid_links, kw->kw_bssids.q_back); kw->kw_num_bssids++; /* remove kw from current position */ remque(&kw->kw_links); /* and insert at end */ insque(&kw->kw_links, kw_list.q_back); nlog(LOG_DEBUG, "kw_update: appended BSSID %s to Known WLAN %s", bssid, kw->kw_essid); return (B_TRUE); } /* * Parses /etc/nwam/known_wifi_nets and populates kw_list, with the oldest * wifi networks first in the list. Returns the number of unique entries * in kw_list (to use for priority values). */ static int parse_known_wifi_nets(void) { FILE *fp; char line[LINE_MAX]; char *cp, *tok[MAX_FIELDS]; int lnum, num_kw = 0; kw_t *kw; kw_list_init(); /* * The file format is: * essid\tbssid (essid followed by tab followed by bssid) */ fp = fopen(KNOWN_WIFI_NETS_FILE, "r"); if (fp == NULL) return (0); for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) { cp = line; while (isspace(*cp)) cp++; if (*cp == '#' || *cp == '\0') continue; if (bufsplit(cp, MAX_FIELDS, tok) != MAX_FIELDS) { syslog(LOG_ERR, "%s:%d: wrong number of tokens; " "ignoring entry", KNOWN_WIFI_NETS_FILE, lnum); continue; } if ((kw = kw_lookup(tok[ESSID])) == NULL) { if (!kw_add(tok[ESSID], tok[BSSID])) { nlog(LOG_ERR, "%s:%d: cannot add entry (%s,%s) to list", KNOWN_WIFI_NETS_FILE, lnum, tok[ESSID], tok[BSSID]); } else { num_kw++; } } else { if (!kw_update(kw, tok[BSSID])) { nlog(LOG_ERR, "%s:%d:cannot update entry (%s,%s) to list", KNOWN_WIFI_NETS_FILE, lnum, tok[ESSID], tok[BSSID]); } } /* next line ... */ } (void) fclose(fp); return (num_kw); } /* * Walk security objects looking for one that matches the essid prefix. * Store the key and keyname if a match is found - we use the last match * as the key for the known WLAN, since it is the most recently updated. */ /* ARGSUSED0 */ static boolean_t find_secobj_matching_prefix(dladm_handle_t dh, void *arg, const char *secobjname) { struct nwamd_secobj_arg *nsa = arg; if (strncmp(nsa->nsa_essid_prefix, secobjname, strlen(nsa->nsa_essid_prefix)) == 0) { nlog(LOG_DEBUG, "find_secobj_matching_prefix: " "found secobj with prefix %s : %s\n", nsa->nsa_essid_prefix, secobjname); /* Free last key found (if any) */ if (nsa->nsa_key != NULL) free(nsa->nsa_key); /* Retrive key so we can get security mode */ nsa->nsa_key = nwamd_wlan_get_key_named(secobjname, 0); (void) strlcpy(nsa->nsa_keyname, secobjname, sizeof (nsa->nsa_keyname)); switch (nsa->nsa_key->wk_class) { case DLADM_SECOBJ_CLASS_WEP: nsa->nsa_secmode = DLADM_WLAN_SECMODE_WEP; nlog(LOG_DEBUG, "find_secobj_matching_prefix: " "got WEP key %s", nsa->nsa_keyname); break; case DLADM_SECOBJ_CLASS_WPA: nsa->nsa_secmode = DLADM_WLAN_SECMODE_WPA; nlog(LOG_DEBUG, "find_secobj_matching_prefix: " "got WPA key %s", nsa->nsa_keyname); break; default: /* shouldn't happen */ nsa->nsa_secmode = DLADM_WLAN_SECMODE_NONE; nlog(LOG_ERR, "find_secobj_matching_prefix: " "key class for key %s was invalid", nsa->nsa_keyname); break; } } return (B_TRUE); } /* Upgrade /etc/nwam/known_wifi_nets file to new libnwam-based config model */ void upgrade_known_wifi_nets_config(void) { kw_t *kw; bssid_t *b; nwam_known_wlan_handle_t kwh; char **bssids; nwam_error_t err; uint64_t priority; int i, num_kw; struct nwamd_secobj_arg nsa; nlog(LOG_INFO, "Upgrading %s to Known WLANs", KNOWN_WIFI_NETS_FILE); /* Parse /etc/nwam/known_wifi_nets */ num_kw = parse_known_wifi_nets(); /* Create Known WLANs for each unique ESSID */ for (kw = (kw_t *)kw_list.q_forw, priority = num_kw-1; kw != (kw_t *)&kw_list; kw = (kw_t *)kw->kw_links.q_forw, priority--) { nwam_value_t priorityval = NULL; nwam_value_t bssidsval = NULL; nwam_value_t secmodeval = NULL; nwam_value_t keynameval = NULL; nlog(LOG_DEBUG, "Creating Known WLAN %s", kw->kw_essid); if ((err = nwam_known_wlan_create(kw->kw_essid, &kwh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade wlan %s: " "could not create known wlan: %s", kw->kw_essid, nwam_strerror(err)); continue; } /* priority of this ESSID */ if ((err = nwam_value_create_uint64(priority, &priorityval)) != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade wlan %s: " "could not create priority value: %s", kw->kw_essid, nwam_strerror(err)); nwam_known_wlan_free(kwh); continue; } err = nwam_known_wlan_set_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_PRIORITY, priorityval); nwam_value_free(priorityval); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade wlan %s: " "could not set priority value: %s", kw->kw_essid, nwam_strerror(err)); nwam_known_wlan_free(kwh); continue; } /* loop through kw->kw_bssids and create an array of bssids */ bssids = calloc(kw->kw_num_bssids, sizeof (char *)); if (bssids == NULL) { nwam_known_wlan_free(kwh); nlog(LOG_ERR, "upgrade wlan %s: " "could not calloc for bssids: %m", kw->kw_essid); continue; } for (b = (bssid_t *)kw->kw_bssids.q_forw, i = 0; b != (bssid_t *)&kw->kw_bssids; b = (bssid_t *)b->bssid_links.q_forw, i++) { bssids[i] = strdup(b->bssid); } /* BSSIDs for this ESSID */ if ((err = nwam_value_create_string_array(bssids, kw->kw_num_bssids, &bssidsval)) != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade wlan %s: " "could not create bssids value: %s", kw->kw_essid, nwam_strerror(err)); for (i = 0; i < kw->kw_num_bssids; i++) free(bssids[i]); free(bssids); nwam_known_wlan_free(kwh); continue; } err = nwam_known_wlan_set_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_BSSIDS, bssidsval); nwam_value_free(bssidsval); for (i = 0; i < kw->kw_num_bssids; i++) free(bssids[i]); free(bssids); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade wlan %s: " "could not set bssids: %s", kw->kw_essid, nwam_strerror(err)); nwam_known_wlan_free(kwh); continue; } /* * Retrieve last key matching ESSID prefix if any, and set * the retrieved key name and security mode. */ nwamd_set_key_name(kw->kw_essid, NULL, nsa.nsa_essid_prefix, sizeof (nsa.nsa_essid_prefix)); nsa.nsa_key = NULL; nsa.nsa_secmode = DLADM_WLAN_SECMODE_NONE; (void) dladm_walk_secobj(dld_handle, &nsa, find_secobj_matching_prefix, DLADM_OPT_PERSIST); if (nsa.nsa_key != NULL) { if ((err = nwam_value_create_string(nsa.nsa_keyname, &keynameval)) == NWAM_SUCCESS) { (void) nwam_known_wlan_set_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_KEYNAME, keynameval); } free(nsa.nsa_key); nwam_value_free(keynameval); } if ((err = nwam_value_create_uint64(nsa.nsa_secmode, &secmodeval)) != NWAM_SUCCESS || (err = nwam_known_wlan_set_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_SECURITY_MODE, secmodeval)) != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade wlan %s: " "could not set security mode: %s", kw->kw_essid, nwam_strerror(err)); nwam_value_free(secmodeval); nwam_known_wlan_free(kwh); continue; } /* commit, no collision checking by libnwam */ err = nwam_known_wlan_commit(kwh, NWAM_FLAG_KNOWN_WLAN_NO_COLLISION_CHECK); nwam_known_wlan_free(kwh); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade wlan %s: " "could not commit wlan: %s", kw->kw_essid, nwam_strerror(err)); } /* next ... */ } kw_list_free(); } nwam_error_t known_wlan_get_keyname(const char *essid, char *name) { nwam_known_wlan_handle_t kwh = NULL; nwam_value_t keynameval = NULL; char *keyname; nwam_error_t err; if ((err = nwam_known_wlan_read(essid, 0, &kwh)) != NWAM_SUCCESS) return (err); if ((err = nwam_known_wlan_get_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_KEYNAME, &keynameval)) == NWAM_SUCCESS && (err = nwam_value_get_string(keynameval, &keyname)) == NWAM_SUCCESS) { (void) strlcpy(name, keyname, NWAM_MAX_VALUE_LEN); } if (keynameval != NULL) nwam_value_free(keynameval); if (kwh != NULL) nwam_known_wlan_free(kwh); return (err); } /* Performs a scan on a wifi link NCU */ /* ARGSUSED */ static int nwamd_ncu_known_wlan_committed(nwamd_object_t object, void *data) { nwamd_ncu_t *ncu_data = object->nwamd_object_data; if (ncu_data->ncu_type != NWAM_NCU_TYPE_LINK) return (0); /* network selection will be done only if possible */ if (ncu_data->ncu_link.nwamd_link_media == DL_WIFI) (void) nwamd_wlan_scan(ncu_data->ncu_name); return (0); } /* Handle known WLAN initialization/refresh event */ /* ARGSUSED */ void nwamd_known_wlan_handle_init_event(nwamd_event_t known_wlan_event) { /* * Since the Known WLAN list has changed, do a rescan so that the * best network is selected. */ (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_known_wlan_committed, NULL); } void nwamd_known_wlan_handle_action_event(nwamd_event_t known_wlan_event) { switch (known_wlan_event->event_msg->nwe_data.nwe_object_action. nwe_action) { case NWAM_ACTION_ADD: case NWAM_ACTION_REFRESH: nwamd_known_wlan_handle_init_event(known_wlan_event); break; case NWAM_ACTION_DESTROY: /* Nothing needs to be done for destroy */ break; /* all other events are invalid for known WLANs */ case NWAM_ACTION_ENABLE: case NWAM_ACTION_DISABLE: default: nlog(LOG_INFO, "nwam_known_wlan_handle_action_event: " "unexpected action"); break; } } int nwamd_known_wlan_action(const char *known_wlan, nwam_action_t action) { nwamd_event_t known_wlan_event = nwamd_event_init_object_action (NWAM_OBJECT_TYPE_KNOWN_WLAN, known_wlan, NULL, action); if (known_wlan_event == NULL) return (1); nwamd_event_enqueue(known_wlan_event); 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. */ #ifndef _KNOWN_WLANS_H #define _KNOWN_WLANS_H #include #include void upgrade_known_wifi_nets_config(void); nwam_error_t known_wlan_get_keyname(const char *, char *); nwam_error_t known_wlan_get_keyslot(const char *, uint_t *); #endif /* _KNOWN_WLANS_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) 2007, 2010, Oracle and/or its affiliates. All rights reserved. */ /* * This file is here for legacy support. */ #include #include #include #include #include #include #include #include #include #include #include #include "known_wlans.h" #include "llp.h" #include "ncu.h" #include "util.h" /* * This file formerly contained the routines that manipulate Link Layer * Profiles (aka LLPs) and various support functions. Now only code * necessary for parsing the legacy /etc/nwam/llp file on upgrade is included, * since this legacy configuration needs to be translated into the User NCP. */ #define OUR_OLD_DHCP_WAIT_TIME_PROP_NAME "dhcp_wait_time" #define OUR_OLD_USE_NET_SVC_PROP_NAME "use_net_svc" #define OUR_OLD_IDLE_TIME_PROP_NAME "idle_time" static struct qelem llp_list; /* * Global variable to hold the highest priority. Need to use the atomic * integer arithmetic functions to update it. */ static uint32_t llp_highest_pri; /* Specifies if static address has been configured in /etc/nwam/llp */ static boolean_t static_configured = B_FALSE; static enum interface_type find_if_type(const char *name) { uint32_t media; enum interface_type type; if (name == NULL) { nlog(LOG_DEBUG, "find_if_type: no ifname; " "returning IF_UNKNOWN"); return (IF_UNKNOWN); } type = IF_WIRED; if (dladm_name2info(dld_handle, name, NULL, NULL, NULL, &media) != DLADM_STATUS_OK) { if (strncmp(name, "ip.tun", 6) == 0 || strncmp(name, "ip6.tun", 7) == 0 || strncmp(name, "ip.6to4tun", 10) == 0) /* * We'll need to update our tunnel detection once * the clearview/tun project is integrated; tunnel * names won't necessarily be ip.tunN. */ type = IF_TUN; } else if (media == DL_WIFI) { type = IF_WIRELESS; } return (type); } static void llp_list_free(void) { llp_t *llp; while (llp_list.q_forw != &llp_list) { llp = (llp_t *)llp_list.q_forw; remque(&llp->llp_links); free(llp->llp_ipv6addrstr); free(llp->llp_ipv4addrstr); free(llp); } } static void initialize_llp(void) { llp_list.q_forw = llp_list.q_back = &llp_list; } static llp_t * llp_lookup(const char *link) { llp_t *llp; if (link == NULL) return (NULL); for (llp = (llp_t *)llp_list.q_forw; llp != (llp_t *)&llp_list; llp = (llp_t *)llp->llp_links.q_forw) { if (strcmp(link, llp->llp_lname) == 0) break; } if (llp == (llp_t *)&llp_list) llp = NULL; return (llp); } /* * Create the named LLP with default settings. Called only in main thread. */ static llp_t * llp_add(const char *name) { llp_t *llp; if ((llp = calloc(1, sizeof (llp_t))) == NULL) { nlog(LOG_ERR, "llp_add: cannot allocate LLP: %m"); return (NULL); } if (strlcpy(llp->llp_lname, name, sizeof (llp->llp_lname)) >= sizeof (llp->llp_lname)) { nlog(LOG_ERR, "llp_add: linkname '%s' too long; ignoring entry", name); free(llp); return (NULL); } llp->llp_fileorder = llp->llp_pri = atomic_add_32_nv(&llp_highest_pri, 1); llp->llp_ipv4src = IPV4SRC_DHCP; llp->llp_type = find_if_type(llp->llp_lname); llp->llp_ipv6onlink = B_TRUE; /* * should be a no-op, but for now, make sure we only * create llps for wired and wireless interfaces. */ if (llp->llp_type != IF_WIRED && llp->llp_type != IF_WIRELESS) { nlog(LOG_ERR, "llp_add: wrong type of interface for %s", name); free(llp); return (NULL); } insque(&llp->llp_links, llp_list.q_back); nlog(LOG_DEBUG, "llp_add: " "created llp for link %s, priority %d", llp->llp_lname, llp->llp_pri); return (llp); } static int parse_llp_config(void) { static const char STATICSTR[] = "static"; static const char DHCP[] = "dhcp"; static const char IPV6[] = "ipv6"; static const char NOIPV6[] = "noipv6"; static const char PRIORITY[] = "priority"; FILE *fp; char line[LINE_MAX]; char *cp, *lasts, *lstr, *srcstr, *addrstr; int lnum; llp_t *llp; initialize_llp(); fp = fopen(LLPFILE, "r+"); if (fp == NULL) { if (errno == ENOENT) return (errno); nlog(LOG_ERR, "parse_llp_config: " "open legacy LLP config file: %m"); return (-1); } for (lnum = 1; fgets(line, sizeof (line), fp) != NULL; lnum++) { if (line[strlen(line) - 1] == '\n') line[strlen(line) - 1] = '\0'; cp = line; while (isspace(*cp)) cp++; if (*cp == '#' || *cp == '\0') continue; nlog(LOG_DEBUG, "parse_llp_config: " "parsing legacy LLP conf file line %d...", lnum); if (((lstr = strtok_r(cp, " \t", &lasts)) == NULL) || ((srcstr = strtok_r(NULL, " \t", &lasts)) == NULL)) { nlog(LOG_ERR, "parse_llp_config: line %d: " "not enough tokens; ignoring entry", lnum); continue; } if ((llp = llp_lookup(lstr)) == NULL && (llp = llp_add(lstr)) == NULL) { nlog(LOG_ERR, "parse_llp_config: line %d: " "cannot add entry", lnum); continue; } if (strcasecmp(srcstr, STATICSTR) == 0) { if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL || atoi(addrstr) == 0) { /* crude check for number */ nlog(LOG_ERR, "parse_llp_config: line %d: " "missing ipaddr for static config", lnum); } else if ((addrstr = strdup(addrstr)) == NULL) { nlog(LOG_ERR, "parse_llp_config: line %d: " "cannot save address", lnum); } else { free(llp->llp_ipv4addrstr); llp->llp_ipv4src = IPV4SRC_STATIC; llp->llp_ipv4addrstr = addrstr; } } else if (strcasecmp(srcstr, DHCP) == 0) { llp->llp_ipv4src = IPV4SRC_DHCP; } else if (strcasecmp(srcstr, IPV6) == 0) { llp->llp_ipv6onlink = B_TRUE; if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) { (void) 0; } else if ((addrstr = strdup(addrstr)) == NULL) { nlog(LOG_ERR, "parse_llp_config: line %d: " "cannot save address", lnum); } else { free(llp->llp_ipv6addrstr); llp->llp_ipv6addrstr = addrstr; } } else if (strcasecmp(srcstr, NOIPV6) == 0) { llp->llp_ipv6onlink = B_FALSE; } else if (strcasecmp(srcstr, PRIORITY) == 0) { if ((addrstr = strtok_r(NULL, " \t", &lasts)) == NULL) { nlog(LOG_ERR, "parse_llp_config: line %d: " "missing priority value", lnum); } else { llp->llp_pri = atoi(addrstr); } } else { nlog(LOG_ERR, "parse_llp_config: line %d: " "unrecognized field '%s'", lnum, srcstr); } } (void) fclose(fp); return (0); } /* * Translate legacy LLP config into the user NCP. */ static int upgrade_llp_config(void) { llp_t *wp; nwam_ncp_handle_t user_ncp; nwam_ncu_handle_t phys_ncu = NULL, ip_ncu = NULL; nwam_error_t err; uint64_t uintval; char *strval; const char *prop; switch (parse_llp_config()) { case -1: return (0); case ENOENT: return (ENOENT); default: break; } err = nwam_ncp_create(NWAM_NCP_NAME_USER, 0, &user_ncp); switch (err) { case NWAM_SUCCESS: break; case NWAM_ERROR_BIND: case NWAM_ERROR_INTERNAL: nlog(LOG_ERR, "upgrade_llp_config: " "could not create User NCP: %s", nwam_strerror(err)); llp_list_free(); return (EAGAIN); default: nlog(LOG_ERR, "upgrade_llp_config: error creating User NCP: %s", nwam_strerror(err)); llp_list_free(); return (0); } nlog(LOG_DEBUG, "upgrade_llp_config: walking llp list"); for (wp = (llp_t *)llp_list.q_forw; wp != (llp_t *)&llp_list; wp = (llp_t *)wp->llp_links.q_forw) { nlog(LOG_DEBUG, "upgrade_llp_config: " "upgrading llp %s", wp->llp_lname); if (nwam_ncu_create(user_ncp, wp->llp_lname, NWAM_NCU_TYPE_INTERFACE, NWAM_NCU_CLASS_IP, &ip_ncu) != NWAM_SUCCESS || nwam_ncu_create(user_ncp, wp->llp_lname, NWAM_NCU_TYPE_LINK, NWAM_NCU_CLASS_PHYS, &phys_ncu) != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade_llp_config: llp %s: " "could not create NCUs: %s", wp->llp_lname, nwam_strerror(err)); break; } /* Link NCU properties */ prop = NWAM_NCU_PROP_ACTIVATION_MODE; uintval = NWAM_ACTIVATION_MODE_PRIORITIZED; if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop)) != NWAM_SUCCESS) break; prop = NWAM_NCU_PROP_PRIORITY_MODE; uintval = NWAM_PRIORITY_MODE_EXCLUSIVE; if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop)) != NWAM_SUCCESS) break; prop = NWAM_NCU_PROP_PRIORITY_GROUP; uintval = wp->llp_pri; if ((err = nwamd_set_ncu_uint(phys_ncu, &uintval, 1, prop)) != NWAM_SUCCESS) break; /* IP NCU properties */ if (wp->llp_ipv4addrstr != NULL) { /* Set v4 address and specify static addrsrc */ prop = NWAM_NCU_PROP_IPV4_ADDRSRC; uintval = NWAM_ADDRSRC_STATIC; if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1, prop)) != NWAM_SUCCESS) break; prop = NWAM_NCU_PROP_IPV4_ADDR; strval = wp->llp_ipv4addrstr; if ((err = nwamd_set_ncu_string(ip_ncu, &strval, 1, prop)) != NWAM_SUCCESS) break; static_configured = B_TRUE; } if (wp->llp_ipv6addrstr != NULL) { /* Set v6 address and specify static addrsrc */ prop = NWAM_NCU_PROP_IPV6_ADDRSRC; uintval = NWAM_ADDRSRC_STATIC; if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1, prop)) != NWAM_SUCCESS) break; prop = NWAM_NCU_PROP_IPV6_ADDR; strval = wp->llp_ipv6addrstr; if ((err = nwamd_set_ncu_string(ip_ncu, &strval, 1, prop)) != NWAM_SUCCESS) break; static_configured = B_TRUE; } if (!wp->llp_ipv6onlink) { prop = NWAM_NCU_PROP_IP_VERSION; uintval = IPV4_VERSION; if ((err = nwamd_set_ncu_uint(ip_ncu, &uintval, 1, prop)) != NWAM_SUCCESS) break; } if ((err = nwam_ncu_commit(ip_ncu, 0)) != NWAM_SUCCESS || (err = nwam_ncu_commit(phys_ncu, 0)) != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade_llp_config: llp %s: " "could not commit NCUs: %s", wp->llp_lname, nwam_strerror(err)); /* Schedule a retry - root filesystem may be readonly */ llp_list_free(); nwam_ncu_free(ip_ncu); nwam_ncu_free(phys_ncu); (void) nwam_ncp_destroy(user_ncp, 0); return (EAGAIN); } } if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "upgrade_llp_config: llp %s: " "could not set value for property %s: %s", wp->llp_lname, prop, nwam_strerror(err)); } llp_list_free(); nwam_ncu_free(ip_ncu); nwam_ncu_free(phys_ncu); nwam_ncp_free(user_ncp); return (0); } /* * Upgrade legacy llp and known_wifi_nets files. Note - it is possible that * the root filesystem is not writable at this point, so we need to schedule * a retry of the upgrade operation in the event that committing the new * config fails. */ /* ARGSUSED0 */ void nwamd_handle_upgrade(nwamd_event_t event) { nwamd_event_t upgrade_event; uint64_t dhcp_wait_time, idle_time; boolean_t use_net_svc; switch (upgrade_llp_config()) { case -1: case ENOENT: /* Nothing readable to upgrade */ break; case EAGAIN: /* * Schedule retry in NWAMD_READONLY_RETRY_INTERVAL seconds * as root fs may be readonly. * * The upgrade event is of type NCU, but has no associated * object (we use the event type to map to the appropriate * event/method mappings, so to find the NCU upgrade event * method we specify type NCU while not specifying an * object since all NCUs have to be upgraded. */ upgrade_event = nwamd_event_init(NWAM_EVENT_TYPE_UPGRADE, NWAM_OBJECT_TYPE_NCP, 0, NULL); if (upgrade_event == NULL) { nlog(LOG_ERR, "nwamd_handle_upgrade: " "could not create retry event to upgrade " "%s configuration", LLPFILE); return; } nwamd_event_enqueue_timed(upgrade_event, NWAMD_READONLY_RETRY_INTERVAL); return; default: break; } /* * If static_configured is set, then at least one static address is * configured in /etc/nwam/llp. Enable the User NCP in this case. */ if (static_configured) { nlog(LOG_DEBUG, "nwamd_handle_upgrade: " "static address configured, enabling User NCP"); (void) pthread_mutex_lock(&active_ncp_mutex); (void) strlcpy(active_ncp, NWAM_NCP_NAME_USER, NWAM_MAX_NAME_LEN); (void) pthread_mutex_unlock(&active_ncp_mutex); } /* upgrade /etc/nwam/known_wifi_nets */ upgrade_known_wifi_nets_config(); /* * SMF property nwamd/dhcp_wait_time in Phase 0/0.5 has been * replaced by nwamd/ncu_wait_time property. If the dhcp_wait_time * property exists (which means it has been changed by the user), * set its value to ncu_wait_time and remove the property. */ if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_OLD_DHCP_WAIT_TIME_PROP_NAME, &dhcp_wait_time) == 0) { (void) nwamd_set_count_property(OUR_FMRI, OUR_PG, OUR_NCU_WAIT_TIME_PROP_NAME, dhcp_wait_time); (void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG, OUR_OLD_DHCP_WAIT_TIME_PROP_NAME); nlog(LOG_DEBUG, "nwamd_handle_upgrade: " "converted '%s' to '%s' with value of %lld", OUR_OLD_DHCP_WAIT_TIME_PROP_NAME, OUR_NCU_WAIT_TIME_PROP_NAME, dhcp_wait_time); } /* * If the user has changed Phase 0/0.5 properties that don't exist in * Phase 1, manifest-import reports a warning; but those properties are * not removed. nwamd/use_net_svc and nwamd/idle_time are two * properties that don't exist in Phase 1. If they exist, remove them. */ if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_OLD_IDLE_TIME_PROP_NAME, &idle_time) == 0) { (void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG, OUR_OLD_IDLE_TIME_PROP_NAME); } if (nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG, OUR_OLD_USE_NET_SVC_PROP_NAME, &use_net_svc) == 0) { (void) nwamd_delete_scf_property(OUR_FMRI, OUR_PG, OUR_OLD_USE_NET_SVC_PROP_NAME); } nlog(LOG_DEBUG, "nwamd_handle_upgrade: " "creating version property, setting to 1\n"); (void) nwamd_set_count_property(OUR_FMRI, OUR_PG, OUR_VERSION_PROP_NAME, 1U); (void) smf_refresh_instance(OUR_FMRI); } /* * 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 _LLP_H #define _LLP_H #include #include #include #include "events.h" /* * This file is here for legacy support. */ #define LLPDIR "/etc/nwam" #define LLPFILE LLPDIR"/llp" enum interface_type { IF_UNKNOWN, IF_WIRED, IF_WIRELESS, IF_TUN }; typedef enum { IPV4SRC_STATIC, IPV4SRC_DHCP } ipv4src_t; /* * This structure contains a representation of legacy LLP configuration * which previously represented the intended configuration of the system as * differentiated from the actual IPv4 configuration of the system represented * by the interface structures. * * llp structures are held on the list llp_head. */ typedef struct llp { struct qelem llp_links; char llp_lname[LIFNAMSIZ]; uint32_t llp_pri; /* lower number => higher priority */ int llp_fileorder; enum interface_type llp_type; ipv4src_t llp_ipv4src; char *llp_ipv4addrstr; /* if ipsrc is STATIC */ char *llp_ipv6addrstr; /* if the user provided a static addr */ boolean_t llp_ipv6onlink; /* true if we plumb up a v6 interface */ } llp_t; extern llp_t *link_layer_profile; void nwamd_handle_upgrade(nwamd_event_t); #endif /* _LLP_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) 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 #include "conditions.h" #include "events.h" #include "objects.h" #include "util.h" /* * loc.c - contains routines which handle location abstraction. */ pthread_mutex_t active_loc_mutex = PTHREAD_MUTEX_INITIALIZER; char active_loc[NWAM_MAX_NAME_LEN]; static int loc_create_init_fini_event(nwam_loc_handle_t loch, void *data) { boolean_t *init = data; char *name; nwamd_event_t event; if (nwam_loc_get_name(loch, &name) != NWAM_SUCCESS) { nlog(LOG_ERR, "loc_init_fini: could not get loc name"); return (0); } event = nwamd_event_init(*init ? NWAM_EVENT_TYPE_OBJECT_INIT : NWAM_EVENT_TYPE_OBJECT_FINI, NWAM_OBJECT_TYPE_LOC, 0, name); if (event != NULL) nwamd_event_enqueue(event); free(name); return (0); } /* * Walk all locs, creating init events for each. */ void nwamd_init_locs(void) { boolean_t init = B_TRUE; /* Unset active location */ (void) pthread_mutex_lock(&active_loc_mutex); active_loc[0] = '\0'; (void) pthread_mutex_unlock(&active_loc_mutex); (void) nwam_walk_locs(loc_create_init_fini_event, &init, 0, NULL); } /* * Walk all locs, creating fini events for each. */ void nwamd_fini_locs(void) { boolean_t init = B_FALSE; (void) nwam_walk_locs(loc_create_init_fini_event, &init, 0, NULL); } static boolean_t loc_is_enabled(nwam_loc_handle_t loch) { nwam_value_t enabledval; boolean_t enabled = B_FALSE; if (nwam_loc_get_prop_value(loch, NWAM_LOC_PROP_ENABLED, &enabledval) != NWAM_SUCCESS) { nlog(LOG_ERR, "loc_is_enabled: could not retrieve " "enabled value"); return (B_FALSE); } if (nwam_value_get_boolean(enabledval, &enabled) != NWAM_SUCCESS) { nlog(LOG_ERR, "loc_is_enabled: could not retrieve " "enabled value"); nwam_value_free(enabledval); return (B_FALSE); } nwam_value_free(enabledval); return (enabled); } static int64_t loc_get_activation_mode(nwam_loc_handle_t loch) { nwam_error_t err; uint64_t activation; nwam_value_t activationval; if (nwam_loc_get_prop_value(loch, NWAM_LOC_PROP_ACTIVATION_MODE, &activationval) != NWAM_SUCCESS) { nlog(LOG_ERR, "loc_get_activation_mode: could not retrieve " "activation mode value"); return (-1); } err = nwam_value_get_uint64(activationval, &activation); nwam_value_free(activationval); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "loc_get_activation_mode: could not retrieve " "activation mode value"); return (-1); } return ((int64_t)activation); } /* Enables the location. */ static void nwamd_loc_activate(const char *object_name) { char *enabled; nlog(LOG_DEBUG, "nwamd_loc_activate: activating loc %s", object_name); /* * Find currently enabled location and change its state to disabled * if it is a manual location, or offline (if it is not). * Only manual locations reach disabled, since conditional and * system locations which are manually disabled simply revert to * their conditions for activation. */ if ((enabled = malloc(NWAM_MAX_NAME_LEN)) != NULL && nwamd_lookup_string_property(NET_LOC_FMRI, NET_LOC_PG, NET_LOC_SELECTED_PROP, enabled, NWAM_MAX_NAME_LEN) == 0) { /* Only change state if current != new */ if (strcmp(enabled, object_name) != 0) { boolean_t do_disable = B_FALSE; nwamd_object_t eobj = nwamd_object_find (NWAM_OBJECT_TYPE_LOC, enabled); if (eobj == NULL) { nlog(LOG_INFO, "nwamd_loc_activate: could not " "find old location %s", enabled); goto skip_disable; } /* * Disable if the old location was manual, since the * only way a manual location can deactivate is if * it is disabled. */ do_disable = (loc_get_activation_mode(eobj->nwamd_object_handle) == (int64_t)NWAM_ACTIVATION_MODE_MANUAL); nwamd_object_release(eobj); if (do_disable) { nlog(LOG_DEBUG, "nwamd_loc_activate: " "disable needed for old location %s", enabled); nwamd_object_set_state (NWAM_OBJECT_TYPE_LOC, enabled, NWAM_STATE_DISABLED, NWAM_AUX_STATE_MANUAL_DISABLE); } else { nlog(LOG_DEBUG, "nwamd_loc_activate: " "offline needed for old location %s", enabled); nwamd_object_set_state (NWAM_OBJECT_TYPE_LOC, enabled, NWAM_STATE_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } } } skip_disable: free(enabled); if (nwamd_set_string_property(NET_LOC_FMRI, NET_LOC_PG, NET_LOC_SELECTED_PROP, object_name) == 0) { char *state = smf_get_state(NET_LOC_FMRI); nlog(LOG_INFO, "nwamd_loc_activate: set %s/%s to %s; " "service is in %s state", NET_LOC_PG, NET_LOC_SELECTED_PROP, object_name, state == NULL ? "unknown" : state); free(state); (void) smf_restore_instance(NET_LOC_FMRI); if (smf_refresh_instance(NET_LOC_FMRI) == 0) { (void) pthread_mutex_lock(&active_loc_mutex); (void) strlcpy(active_loc, object_name, sizeof (active_loc)); (void) pthread_mutex_unlock(&active_loc_mutex); nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, object_name, NWAM_STATE_ONLINE, NWAM_AUX_STATE_ACTIVE); } else { nlog(LOG_ERR, "nwamd_loc_activate: " "%s could not be refreshed", NET_LOC_FMRI); nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, object_name, NWAM_STATE_MAINTENANCE, NWAM_AUX_STATE_METHOD_FAILED); } } } struct nwamd_loc_check_walk_arg { nwamd_object_t winning_object; uint64_t winning_rating; }; /* * Determine which location should be activated. */ static int nwamd_loc_check(nwamd_object_t object, void *data) { struct nwamd_loc_check_walk_arg *wa = data; nwam_loc_handle_t loch = object->nwamd_object_handle; nwam_value_t conditionval; int64_t lactivation; uint64_t rating, activation; boolean_t satisfied; char **conditions; uint_t nelem; lactivation = loc_get_activation_mode(object->nwamd_object_handle); if (lactivation == -1) return (0); activation = (uint64_t)lactivation; switch (activation) { case NWAM_ACTIVATION_MODE_MANUAL: if (loc_is_enabled(loch)) { /* Manually enabled locations should always win out. */ nlog(LOG_DEBUG, "nwamd_loc_check: %s is enabled", object->nwamd_object_name); wa->winning_object = object; wa->winning_rating = UINT64_MAX; } else { nlog(LOG_DEBUG, "nwamd_loc_check: %s is disabled", object->nwamd_object_name); if (object->nwamd_object_state != NWAM_STATE_DISABLED) { nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, object->nwamd_object_name, NWAM_STATE_DISABLED, NWAM_AUX_STATE_MANUAL_DISABLE); } } return (0); case NWAM_ACTIVATION_MODE_CONDITIONAL_ANY: case NWAM_ACTIVATION_MODE_CONDITIONAL_ALL: if (loc_is_enabled(loch)) { /* Manually enabled locations should always win out. */ nlog(LOG_DEBUG, "nwamd_loc_check: %s is enabled", object->nwamd_object_name); wa->winning_object = object; wa->winning_rating = UINT64_MAX; } if (nwam_loc_get_prop_value(loch, NWAM_LOC_PROP_CONDITIONS, &conditionval) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_loc_check: could not retrieve " "condition value"); return (0); } if (nwam_value_get_string_array(conditionval, &conditions, &nelem) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_loc_check: could not retrieve " "condition value"); nwam_value_free(conditionval); return (0); } satisfied = nwamd_check_conditions(activation, conditions, nelem); if (satisfied) { rating = nwamd_rate_conditions(activation, conditions, nelem); if (rating > wa->winning_rating) { wa->winning_object = object; wa->winning_rating = rating; } } nwam_value_free(conditionval); return (0); case NWAM_ACTIVATION_MODE_SYSTEM: if (loc_is_enabled(loch)) { /* Manually enabled locations should always win out. */ nlog(LOG_DEBUG, "nwamd_loc_check: %s is enabled", object->nwamd_object_name); wa->winning_object = object; wa->winning_rating = UINT64_MAX; } /* Either NoNet, Automatic or Legacy location, so skip. */ return (0); default: return (0); } /*NOTREACHED*/ return (0); } static int nwamd_ncu_online_check(nwamd_object_t object, void *data) { boolean_t *online = data; nwamd_ncu_t *ncu_data = object->nwamd_object_data; if (ncu_data->ncu_type != NWAM_NCU_TYPE_INTERFACE) return (0); if (object->nwamd_object_state == NWAM_STATE_ONLINE) { /* An online IP NCU found, stop walk */ *online = B_TRUE; return (1); } return (0); } void nwamd_loc_check_conditions(void) { struct nwamd_loc_check_walk_arg wa = { NULL, 0 }; const char *winning_loc; boolean_t ncu_online = B_FALSE; boolean_t is_active; /* * Walk the NCUs to find out if at least one IP NCU is online. If so, * check the activation-mode and conditions. If not, enable the NoNet * location. */ (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_online_check, &ncu_online); if (!ncu_online) { winning_loc = NWAM_LOC_NAME_NO_NET; } else { (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_LOC, nwamd_loc_check, &wa); if (wa.winning_object != NULL) winning_loc = wa.winning_object->nwamd_object_name; else winning_loc = NWAM_LOC_NAME_AUTOMATIC; } nlog(LOG_DEBUG, "nwamd_loc_check_conditions: winning loc is %s", winning_loc); /* If the winning location is already active, do nothing */ (void) pthread_mutex_lock(&active_loc_mutex); is_active = (strcmp(active_loc, winning_loc) == 0); (void) pthread_mutex_unlock(&active_loc_mutex); if (is_active) return; nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, winning_loc, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_METHOD_RUNNING); } int nwamd_loc_action(const char *loc, nwam_action_t action) { nwamd_event_t event = nwamd_event_init_object_action (NWAM_OBJECT_TYPE_LOC, loc, NULL, action); if (event == NULL) return (1); nwamd_event_enqueue(event); return (0); } /* * Event handling functions. */ /* Handle loc initialization/refresh event */ void nwamd_loc_handle_init_event(nwamd_event_t event) { nwamd_object_t object; nwam_loc_handle_t loch; nwam_error_t err; boolean_t new_enabled, old_enabled = B_FALSE; nwam_state_t state; if ((err = nwam_loc_read(event->event_object, 0, &loch)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_loc_handle_init_event: could not " "read object '%s': %s", event->event_object, nwam_strerror(err)); nwamd_event_do_not_send(event); return; } if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC, event->event_object)) != NULL) { old_enabled = loc_is_enabled(object->nwamd_object_handle); nwam_loc_free(object->nwamd_object_handle); object->nwamd_object_handle = loch; } else { object = nwamd_object_init(NWAM_OBJECT_TYPE_LOC, event->event_object, loch, NULL); object->nwamd_object_state = NWAM_STATE_OFFLINE; object->nwamd_object_aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; } new_enabled = loc_is_enabled(loch); state = object->nwamd_object_state; nwamd_object_release(object); /* * If this location is ONLINE and the value of the "enabled" property * has not changed, then this location is getting refreshed because it * was committed with changes. Change states to re-activate itself. * If the "enabled" property has changed, then this location is * getting refreshed as part of a enable/disable action and there is * no need to change states here. */ if (state == NWAM_STATE_ONLINE && old_enabled == new_enabled) { nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_METHOD_RUNNING); } } /* Handle loc finish event */ void nwamd_loc_handle_fini_event(nwamd_event_t event) { nwamd_object_t object; nlog(LOG_DEBUG, "nwamd_loc_handle_fini_event(%s)", event->event_object); /* Don't disable the location, as this can enable the Automatic loc */ if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC, event->event_object)) == NULL) { nlog(LOG_INFO, "nwamd_loc_handle_fini_event: " "loc %s not found", event->event_object); nwamd_event_do_not_send(event); return; } nwamd_object_release_and_destroy(object); } void nwamd_loc_handle_action_event(nwamd_event_t event) { nwamd_object_t object; switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) { case NWAM_ACTION_ENABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_loc_handle_action_event: " "could not find location %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_ONLINE) { nlog(LOG_DEBUG, "nwamd_loc_handle_action_event: " "location %s already online, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_METHOD_RUNNING); break; case NWAM_ACTION_DISABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_loc_handle_action_event: " "could not find location %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_DISABLED) { nlog(LOG_DEBUG, "nwamd_loc_handle_action_event: " "location %s already disabled, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_LOC, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_MANUAL_DISABLE); break; case NWAM_ACTION_ADD: case NWAM_ACTION_REFRESH: nwamd_loc_handle_init_event(event); break; case NWAM_ACTION_DESTROY: nwamd_loc_handle_fini_event(event); break; default: nlog(LOG_INFO, "nwam_loc_handle_action_event: " "unexpected action"); break; } } void nwamd_loc_handle_state_event(nwamd_event_t event) { nwamd_object_t object; nwam_state_t new_state; nwam_aux_state_t new_aux_state; if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_LOC, event->event_object)) == NULL) { nlog(LOG_INFO, "nwamd_loc_handle_state_event: " "state event for nonexistent loc %s", event->event_object); nwamd_event_do_not_send(event); return; } new_state = event->event_msg->nwe_data.nwe_object_state.nwe_state; new_aux_state = event->event_msg->nwe_data.nwe_object_state.nwe_aux_state; if (new_state == object->nwamd_object_state && new_aux_state == object->nwamd_object_aux_state) { nlog(LOG_DEBUG, "nwamd_loc_handle_state_event: " "loc %s already in state (%s , %s)", object->nwamd_object_name, nwam_state_to_string(new_state), nwam_aux_state_to_string(new_aux_state)); nwamd_object_release(object); return; } object->nwamd_object_state = new_state; object->nwamd_object_aux_state = new_aux_state; nlog(LOG_DEBUG, "nwamd_loc_handle_state_event: changing state for loc " "%s to (%s , %s)", object->nwamd_object_name, nwam_state_to_string(object->nwamd_object_state), nwam_aux_state_to_string(object->nwamd_object_aux_state)); nwamd_object_release(object); /* * State machine for location. */ switch (new_state) { case NWAM_STATE_OFFLINE_TO_ONLINE: nwamd_loc_activate(event->event_object); break; case NWAM_STATE_ONLINE_TO_OFFLINE: /* * Don't need to deactivate current location - condition check * will activate another. If the currently active location is * being deactivated, then it is being manually deactivated; * so also clear active_loc so condition checking is not * confused. */ (void) pthread_mutex_lock(&active_loc_mutex); if (strcmp(event->event_object, active_loc) == 0) active_loc[0] = '\0'; (void) pthread_mutex_unlock(&active_loc_mutex); nwamd_loc_check_conditions(); break; case NWAM_STATE_DISABLED: case NWAM_STATE_OFFLINE: case NWAM_STATE_UNINITIALIZED: case NWAM_STATE_MAINTENANCE: case NWAM_STATE_DEGRADED: default: /* do nothing */ break; } } /* * 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. */ #include #include #include #include #include #include "util.h" /* * logging.c - contains various logging functions. */ boolean_t debug = B_TRUE; /* * This idea for having this function is so that you can drop a dtrace probe * here and trace complete strings (not just those containing formatting). Its * important that we actually format the debug strings so we could trace them * even if we choose not to send them to syslog. */ static void log_out(int severity, const char *str) { if (severity == LOG_DEBUG && !debug) return; syslog(severity, str); } static void log_format(int severity, const char *fmt, va_list ap, char *buf, int bufsize) { int offset; char vbuf[256]; if (buf == NULL) { buf = vbuf; bufsize = sizeof (vbuf); } offset = snprintf(buf, bufsize, "%d: ", pthread_self()); (void) vsnprintf(buf + offset, bufsize - offset, fmt, ap); log_out(severity, buf); } /* * This function takes a syslog severity and uses it to determine what to do * with the message (currently send it to syslog). */ void nlog(int severity, const char *fmt, ...) { va_list ap; va_start(ap, fmt); log_format(severity, fmt, ap, NULL, 0); va_end(ap); } void pfail(const char *fmt, ...) { char *msg; va_list ap; msg = malloc(256); va_start(ap, fmt); log_format(LOG_ERR, fmt, ap, msg, 256); va_end(ap); if (msg == NULL) msg = "ran out of memory exiting. see log."; (void) puts(msg); 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) 2007, 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 #include #include "conditions.h" #include "events.h" #include "llp.h" #include "ncp.h" #include "objects.h" #include "util.h" /* * nwamd - NetWork Auto-Magic Daemon */ boolean_t fg = B_FALSE; dladm_handle_t dld_handle = NULL; ipadm_handle_t ipadm_handle = NULL; boolean_t shutting_down = B_FALSE; sigset_t original_sigmask; static sigset_t sigwaitset; static void nwamd_refresh(void); static void graceful_shutdown(void); /* * nwamd * * This is the Network Auto-Magic daemon. For further high level information * see the Network Auto-Magic project and the Approachability communities * on opensolaris.org, nwamd(8), and the README in the source directory. * * The general structure of the code is as a set of event source threads * which feed events into the event handling thread. Some of these events * are internal-only (e.g UPGRADE), but some also involve propogation * to external listeners (who register via a door call into the daemon). * * signal management * Due to being threaded, a simple set of signal handlers would not work * very well for nwamd. Instead nwamd blocks signals in all but the * signal handling thread at startup. * */ /* * In this file there are several utility functions which might otherwise * belong in util.c, but since they are only called from main(), they can * live here as static functions: * - nlog set-up * - daemonizing * - looking up smf(7) properties * - signal handling * - managing privileges(7) */ static void start_logging(void) { openlog("nwamd", LOG_PID | LOG_NDELAY, LOG_DAEMON); } 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) pfail("fork 1 failed"); if (pid != 0) { (void) wait(NULL); nlog(LOG_DEBUG, "child %ld exited, daemonizing", pid); _exit(0); } if (setsid() == (pid_t)-1) pfail("setsid"); if ((pid = fork()) == (pid_t)-1) pfail("fork 2 failed"); if (pid != 0) { _exit(0); } (void) chdir("/"); (void) umask(022); } /* ARGSUSED */ static void * sighandler(void *arg) { int sig; while (!shutting_down) { sig = sigwait(&sigwaitset); nlog(LOG_DEBUG, "signal %s caught", strsignal(sig)); switch (sig) { case SIGTHAW: case SIGHUP: /* * Resumed from suspend or refresh. Clear up all * objects so their states start from scratch; * then refresh(). */ nwamd_fini_enms(); nwamd_fini_ncus(); nwamd_fini_locs(); nwamd_refresh(); break; case SIGUSR1: /* * Undocumented "log ncu list" signal. */ nwamd_log_ncus(); break; case SIGTERM: nlog(LOG_DEBUG, "%s received, shutting down", strsignal(sig)); graceful_shutdown(); break; default: nlog(LOG_DEBUG, "unexpected signal %s received, " "ignoring", strsignal(sig)); break; } } return (NULL); } static void init_signalhandling(void) { pthread_attr_t attr; pthread_t sighand; int err; (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (err = pthread_create(&sighand, &attr, sighandler, NULL)) { nlog(LOG_ERR, "pthread_create system: %s", strerror(err)); exit(EXIT_FAILURE); } else { nlog(LOG_DEBUG, "signal handler thread: %d", sighand); } (void) pthread_attr_destroy(&attr); } /* * Construct the set of signals that we explicitly want to deal with. * We block these while we're still single-threaded; this block will * be inherited by all the threads we create. When we are ready to * start handling signals, we will start the signal handling thread, * which will sigwait() this same set of signals, and will thus receive * and handle any that are sent to the process. */ static void block_signals(void) { (void) sigemptyset(&sigwaitset); (void) sigaddset(&sigwaitset, SIGHUP); (void) sigaddset(&sigwaitset, SIGUSR1); (void) sigaddset(&sigwaitset, SIGUSR2); (void) sigaddset(&sigwaitset, SIGTERM); (void) sigaddset(&sigwaitset, SIGTHAW); (void) pthread_sigmask(SIG_BLOCK, &sigwaitset, &original_sigmask); } /* * Look up nwamd property values and set daemon variables appropriately. * This function will be called on startup and via the signal handling * thread on receiving a HUP (which occurs when the nwam service is * refreshed). */ static void lookup_daemon_properties(void) { char *active_ncp_tmp; char *scan_level_tmp; (void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG, OUR_DEBUG_PROP_NAME, &debug); (void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG, OUR_AUTOCONF_PROP_NAME, &wireless_autoconf); (void) nwamd_lookup_boolean_property(OUR_FMRI, OUR_PG, OUR_STRICT_BSSID_PROP_NAME, &wireless_strict_bssid); (void) pthread_mutex_lock(&active_ncp_mutex); if ((active_ncp_tmp = malloc(NWAM_MAX_NAME_LEN)) == NULL || nwamd_lookup_string_property(OUR_FMRI, OUR_PG, OUR_ACTIVE_NCP_PROP_NAME, active_ncp_tmp, NWAM_MAX_NAME_LEN) != 0) { (void) strlcpy(active_ncp, NWAM_NCP_NAME_AUTOMATIC, NWAM_MAX_NAME_LEN); } else { (void) strlcpy(active_ncp, active_ncp_tmp, NWAM_MAX_NAME_LEN); } (void) pthread_mutex_unlock(&active_ncp_mutex); free(active_ncp_tmp); if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_CONDITION_CHECK_INTERVAL_PROP_NAME, &condition_check_interval) != 0) condition_check_interval = CONDITION_CHECK_INTERVAL_DEFAULT; if ((scan_level_tmp = malloc(NWAM_MAX_NAME_LEN)) == NULL || nwamd_lookup_string_property(OUR_FMRI, OUR_PG, OUR_WIRELESS_SCAN_LEVEL_PROP_NAME, scan_level_tmp, NWAM_MAX_NAME_LEN) != 0) { wireless_scan_level = WIRELESS_SCAN_LEVEL_DEFAULT; } else { if (dladm_wlan_str2strength(scan_level_tmp, &wireless_scan_level) != DLADM_STATUS_OK) wireless_scan_level = DLADM_WLAN_STRENGTH_VERY_WEAK; } free(scan_level_tmp); if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_WIRELESS_SCAN_INTERVAL_PROP_NAME, &wireless_scan_interval) != 0) wireless_scan_interval = WIRELESS_SCAN_INTERVAL_DEFAULT; if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_NCU_WAIT_TIME_PROP_NAME, &ncu_wait_time) != 0) ncu_wait_time = NCU_WAIT_TIME_DEFAULT; nlog(LOG_DEBUG, "Read daemon configuration properties."); } /* * Re-read the SMF properties. * Reset ncu priority group (since the NCUs will have to walk * through their state machines again) and schedule a check * Re-read objects from libnwam. * Also, run condition checking for locations and ENMs. */ static void nwamd_refresh(void) { lookup_daemon_properties(); (void) pthread_mutex_lock(&active_ncp_mutex); current_ncu_priority_group = INVALID_PRIORITY_GROUP; (void) pthread_mutex_unlock(&active_ncp_mutex); nwamd_init_ncus(); nwamd_init_enms(); nwamd_init_locs(); nwamd_create_ncu_check_event(0); nwamd_create_triggered_condition_check_event(0); } static void graceful_shutdown(void) { nwamd_event_t event; shutting_down = B_TRUE; nwamd_event_sources_fini(); nwamd_door_fini(); nwamd_fini_enms(); nwamd_fini_ncus(); nwamd_fini_locs(); event = nwamd_event_init_shutdown(); if (event == NULL) pfail("nwamd could not create shutdown event, exiting"); nwamd_event_enqueue(event); } int main(int argc, char *argv[]) { int c; uint64_t version; nwamd_event_t event; dladm_status_t drc; ipadm_status_t irc; uid_t uid = getuid(); /* * Block the signals we care about (and which might cause us to * exit based on default disposition) until we're ready to start * handling them properly...see init_signalhandling() below. */ block_signals(); if (uid != UID_NETADM && uid != 0) { /* * This shouldn't happen normally. On upgrade the service might * need reloading. */ pfail("nwamd should run as uid %d, not uid %d\n", UID_NETADM, uid); } (void) setlocale(LC_ALL, ""); (void) textdomain(TEXT_DOMAIN); start_logging(); nlog(LOG_INFO, "nwamd pid %d started", getpid()); while ((c = getopt(argc, argv, "fs:")) != -1) { switch (c) { case 'f': fg = B_TRUE; break; default: nlog(LOG_ERR, "unrecognized option %c", optopt); break; } } lookup_daemon_properties(); if (!fg) daemonize(); /* * The dladm handle *must* be opened before privileges are dropped. * The device privilege requirements, which are stored in * /etc/security/device_policy, may not be loaded yet, as that's * done by svc:/system/filesystem/root. If they are not loaded, * then one must have *all* privs in order to open /dev/dld, which * is one of the steps performed in dladm_open(). */ drc = dladm_open(&dld_handle); if (drc != DLADM_STATUS_OK) { char status_str[DLADM_STRSIZE]; pfail("failed to open dladm handle: %s", dladm_status2str(drc, status_str)); } irc = ipadm_open(&ipadm_handle, 0); if (irc != IPADM_SUCCESS) pfail("failed to open ipadm handle: %s", ipadm_status2str(irc)); /* * Create the event queue before starting event sources, including * signal handling, so we are ready to handle incoming events. Also * start before attempting to upgrade, in case there's a problem * upgrading and we need to retry (in which case we schedule an event * to do so). */ nwamd_event_queue_init(); /* * Handle upgrade of legacy config. Absence of version property * (which did not exist in phase 0 or 0.5) is the indication that * we need to upgrade to phase 1 (version 1). */ if (nwamd_lookup_count_property(OUR_FMRI, OUR_PG, OUR_VERSION_PROP_NAME, &version) != 0) nwamd_handle_upgrade(NULL); /* * Initialize lists handling internal representations of objects. */ nwamd_object_lists_init(); init_signalhandling(); /* Enqueue init event */ event = nwamd_event_init_init(); if (event == NULL) pfail("nwamd could not create init event, exiting"); nwamd_event_enqueue(event); /* * Collect initial user configuration. */ /* * Walk the physical interfaces and update the Automatic NCP to * contain the IP and link NCUs for the interfaces that exist in * the system. */ nwamd_walk_physical_configuration(); /* * We should initialize the door at the point that we can respond to * user requests about the system but before we start actually process * state changes or effecting the system. */ nwamd_door_init(); /* * Initialize data objects. * * Enabling an NCP involves refreshing nwam, which initializes the * objects (ncu, enm, loc, known wlan). Thus, no need to * explicitly initialize these objects here. The refresh also * enqueues and NCU activation checking event. Location and ENM * condition checking are triggered by changes in NCU states. */ (void) pthread_mutex_lock(&active_ncp_mutex); if (nwamd_ncp_action(active_ncp, NWAM_ACTION_ENABLE) != 0) pfail("Initial enable failed for active NCP %s", active_ncp); (void) pthread_mutex_unlock(&active_ncp_mutex); /* * Enqueue an event to start periodic checking of activation conditions. */ nwamd_create_timed_condition_check_event(); /* * These two routines safely minimize our privilege set. They * use reference counting to be safe in a threaded program. It is * gross that we escalate/deescalate to initialize this functionality * but a real fix is to add functionality to do fine grained privs * (and if necessary set uid to 0) in this threaded daemon. */ nwamd_escalate(); nwamd_deescalate(); /* * Start the various agents (hooks on fds, threads) which collect events */ nwamd_event_sources_init(); /* * nwamd_event_handler() only returns on shutdown. */ nwamd_event_handler(); ipadm_close(ipadm_handle); dladm_close(dld_handle); return (EXIT_SUCCESS); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include "conditions.h" #include "events.h" #include "objects.h" #include "ncp.h" #include "ncu.h" #include "util.h" /* * ncp.c - handles NCP actions. */ char active_ncp[NWAM_MAX_NAME_LEN]; nwam_ncp_handle_t active_ncph = NULL; int64_t current_ncu_priority_group = INVALID_PRIORITY_GROUP; /* * active_ncp_mutex protects active_ncp, active_ncph and * current_ncu_priority_group. */ pthread_mutex_t active_ncp_mutex = PTHREAD_MUTEX_INITIALIZER; /* * The variable ncu_wait_time specifies how long to wait to obtain a * DHCP lease before giving up on that NCU and moving on to the next/lower * priority-group. */ uint64_t ncu_wait_time = NCU_WAIT_TIME_DEFAULT; /* * Specifies if this is the first time the NCP has been enabled. True * on startup so that we can differentiate between when we start up * with a given NCP versus when we are asked to reenable it. */ boolean_t initial_ncp_enable = B_TRUE; /* * nwamd_ncp_handle_enable_event() should be called in the event handling * loop in response to an _ENABLE event, triggered as a result of an * nwam_ncp_enable() call from a libnwam consumer. To enable the new NCP, * we first call nwamd_fini_ncus() on the old NCP. This results in enqueueing * of a set of _FINI events for each NCU. These events are handled and in * order to tear down config, (online*, uninitialized) state change events * are created and consumed directly by the fini event handler (these events * are not enqueued as this would result in state events for the old NCP * appearing after the new NCP has been enabled. After the _FINI events are * enqueued, we enqueue an NCP _OBJECT_STATE event for the new NCP. Since * it is enqueued after the _FINI events, we are guaranteed no events for the * old NCP will appear after the new NCP is activated. */ void nwamd_ncp_handle_enable_event(nwamd_event_t event) { char *new_ncp = event->event_object; nwam_ncp_handle_t new_ncph; nwam_error_t err; if (new_ncp[0] == '\0') return; (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(active_ncp, new_ncp) == 0 && !initial_ncp_enable) { nlog(LOG_DEBUG, "nwamd_ncp_handle_enable_event: " "%s is already active", new_ncp); (void) pthread_mutex_unlock(&active_ncp_mutex); return; } (void) pthread_mutex_unlock(&active_ncp_mutex); nlog(LOG_DEBUG, "nwamd_ncp_handle_enable_event: activating NCP %s", new_ncp); /* * To activate new NCP, run nwamd_fini_ncus(), reset the active * priority-group, set the active_ncp property and refresh the * daemon. The refresh action will trigger a re-read of the NCUs * for the activated NCP. */ nwamd_fini_ncus(); if ((err = nwam_ncp_read(new_ncp, 0, &new_ncph)) == NWAM_ENTITY_NOT_FOUND) { err = nwam_ncp_create(new_ncp, 0, &new_ncph); } if (err == NWAM_SUCCESS) { nwam_ncp_free(new_ncph); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCP, new_ncp, NWAM_STATE_ONLINE, NWAM_AUX_STATE_ACTIVE); } else if (initial_ncp_enable) { /* * We weren't able to enable the NCP when nwamd starts up, * retry in a few seconds. */ nwamd_event_t retry_event = nwamd_event_init_object_action (NWAM_OBJECT_TYPE_NCP, new_ncp, NULL, NWAM_ACTION_ENABLE); if (retry_event == NULL) { nlog(LOG_ERR, "nwamd_ncp_handle_enable_event: " "could not create retry event to enable %s NCP", new_ncp); return; } nlog(LOG_ERR, "nwamd_ncp_handle_enable_event: " "failed to enable %s NCP, retrying in %d seconds", new_ncp, NWAMD_READONLY_RETRY_INTERVAL); nwamd_event_enqueue_timed(retry_event, NWAMD_READONLY_RETRY_INTERVAL); } else { nlog(LOG_ERR, "nwamd_ncp_handle_enable_event: error %s", nwam_strerror(err)); return; } } void nwamd_ncp_handle_action_event(nwamd_event_t event) { switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) { case NWAM_ACTION_ENABLE: nwamd_ncp_handle_enable_event(event); break; case NWAM_ACTION_ADD: case NWAM_ACTION_DESTROY: /* nothing to do */ break; default: nlog(LOG_INFO, "nwam_ncp_handle_action_event: " "unexpected action"); nwamd_event_do_not_send(event); break; } } /* * The only state events we create are (online, active) events which are * generated as part of an NCP enable action (see above). */ void nwamd_ncp_handle_state_event(nwamd_event_t event) { char *new_ncp = event->event_object; nwam_ncp_handle_t new_ncph, old_ncph; nwam_error_t err; /* The NCP to be activated should always exist. */ if ((err = nwam_ncp_read(new_ncp, 0, &new_ncph)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncp_handle_state_event: " "cannot read NCP %s: : %s", new_ncp, nwam_strerror(err)); nwamd_event_do_not_send(event); return; } /* * To activate new NCP, reset the active priority-group, set the * active_ncp property and refresh the daemon. The refresh action will * trigger a re-read of the NCUs for the activated NCP. */ (void) pthread_mutex_lock(&active_ncp_mutex); old_ncph = active_ncph; active_ncph = new_ncph; nwam_ncp_free(old_ncph); current_ncu_priority_group = INVALID_PRIORITY_GROUP; (void) strlcpy(active_ncp, event->event_object, sizeof (active_ncp)); (void) pthread_mutex_unlock(&active_ncp_mutex); (void) nwamd_set_string_property(OUR_FMRI, OUR_PG, OUR_ACTIVE_NCP_PROP_NAME, new_ncp); (void) smf_refresh_instance(OUR_FMRI); initial_ncp_enable = B_FALSE; } int nwamd_ncp_action(const char *ncp, nwam_action_t action) { nwamd_event_t event = nwamd_event_init_object_action (NWAM_OBJECT_TYPE_NCP, ncp, NULL, action); if (event == NULL) return (1); nwamd_event_enqueue(event); return (0); } /* * Below this point are routines handling NCU prioritization * policy for the active NCP. */ struct priority_group_cbarg { uint64_t minpriority; uint64_t currpriority; boolean_t found; }; /* Callback used to find next pg in NCP that is >= start_pg */ static int find_next_priority_group_cb(nwamd_object_t object, void *data) { struct priority_group_cbarg *cbarg = data; uint64_t priority; nwamd_ncu_t *ncu = object->nwamd_object_data; if (ncu->ncu_link.nwamd_link_activation_mode != NWAM_ACTIVATION_MODE_PRIORITIZED) return (0); priority = ncu->ncu_link.nwamd_link_priority_group; if (priority >= cbarg->minpriority && priority < cbarg->currpriority) { cbarg->found = B_TRUE; cbarg->currpriority = priority; } return (0); } /* Set current_pg to next pg in NCP that is >= start_pg */ boolean_t nwamd_ncp_find_next_priority_group(int64_t minpriority, int64_t *nextpriorityp) { struct priority_group_cbarg cbarg; cbarg.minpriority = minpriority; cbarg.currpriority = MAXINT; cbarg.found = B_FALSE; (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, find_next_priority_group_cb, &cbarg); if (cbarg.found) { nlog(LOG_DEBUG, "nwamd_ncp_find_next_priority_group: " "next priority group >= %lld is %lld", minpriority, cbarg.currpriority); *nextpriorityp = cbarg.currpriority; return (B_TRUE); } else { nlog(LOG_DEBUG, "nwamd_ncp_find_next_priority_group: " "no priority groups >= %lld exist", minpriority); return (B_FALSE); } } /* * Struct for walking NCUs in the selected priority group. We count * how many of the exclusive, all and shared NCUs are online, and * if activate_or_deactivate is true, we either activate or deactivate * (depending on the value of activate) offline/online NCUs. */ struct nwamd_ncu_check_walk_arg { boolean_t manual; /* enable manual NCUs only */ int64_t priority_group; /* interested priority-group for this walk */ uint64_t exclusive_ncus; uint64_t exclusive_online_ncus; uint64_t shared_ncus; uint64_t shared_online_ncus; uint64_t all_ncus; uint64_t all_online_ncus; boolean_t activate_or_deactivate; boolean_t activate; }; /* * This function serves a number of purposes: * - it supports activation/deactivation of manual NCUs in the current NCP * (when wa->manual is true, wa->activate determines if we activate or * deactivate the current NCU) * - it supports checking/activation of a particular priority group in * the active NCP. This works as follows: * * Count up numbers of exclusive, shared and all NCUs, and how many of each * are online. If an NCU is waiting for IP address to be assigned, it is * also considered online. If activate_or_deactivate is true, we also * either activate (if activate is true) or deactivate prioritized NCUs * that are offline or online. */ static int nwamd_ncu_check_or_activate(nwamd_object_t object, void *data) { struct nwamd_ncu_check_walk_arg *wa = data; nwamd_ncu_t *ncu; uint64_t priority_group, priority_mode; nwamd_object_t if_obj; nwam_state_t state, if_state; nwam_aux_state_t aux_state, if_aux_state; char *name; state = object->nwamd_object_state; aux_state = object->nwamd_object_aux_state; name = object->nwamd_object_name; ncu = object->nwamd_object_data; /* skip NCUs in UNINITIALIZED state */ if (state == NWAM_STATE_UNINITIALIZED) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "skipping uninitialized ncu %s", name); return (0); } if (!wa->manual && wa->priority_group == INVALID_PRIORITY_GROUP) return (0); if (ncu->ncu_type != NWAM_NCU_TYPE_LINK) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "skipping interface NCU %s", name); return (0); } if (!wa->manual && ncu->ncu_link.nwamd_link_activation_mode != NWAM_ACTIVATION_MODE_PRIORITIZED) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "skipping non-prioritized NCU %s", name); return (0); } if (wa->manual && ncu->ncu_link.nwamd_link_activation_mode != NWAM_ACTIVATION_MODE_MANUAL) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "skipping non-manual NCU %s", name); return (0); } priority_group = ncu->ncu_link.nwamd_link_priority_group; priority_mode = ncu->ncu_link.nwamd_link_priority_mode; /* Only work with NCUs in the requested priority-group */ if (!wa->manual && priority_group != wa->priority_group) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "skipping NCU %s in different priority-group", name); return (0); } /* Get the state of the corresponding interface NCU */ if ((if_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_INTERFACE, ncu->ncu_name)) == NULL) { nlog(LOG_ERR, "nwamd_ncu_check_or_activate: " "interface NCU of %s not found, skipping", name); return (0); } if_state = if_obj->nwamd_object_state; if_aux_state = if_obj->nwamd_object_aux_state; nwamd_object_release(if_obj); nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: %s ncu %s", wa->activate_or_deactivate ? (wa->activate ? "activating" : "deactivating") : "checking", name); if (wa->manual) { if (wa->activate_or_deactivate && wa->activate) { if (state == NWAM_STATE_OFFLINE && ncu->ncu_enabled) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "moving NCU %s to offline* from offline", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } if (state != NWAM_STATE_DISABLED && !ncu->ncu_enabled) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "moving NCU %s to online* (disabling)", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_MANUAL_DISABLE); } } return (0); } switch (priority_mode) { case NWAM_PRIORITY_MODE_EXCLUSIVE: wa->exclusive_ncus++; if (state == NWAM_STATE_ONLINE && (if_state == NWAM_STATE_ONLINE || if_aux_state == NWAM_AUX_STATE_IF_WAITING_FOR_ADDR)) wa->exclusive_online_ncus++; /* * For exclusive NCUs, we activate offline NCUs as long * as no other exclusive NCUs are active. */ if (wa->activate_or_deactivate && wa->activate) { if (state == NWAM_STATE_OFFLINE && wa->exclusive_online_ncus == 0) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "moving NCU %s to offline* from offline", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } } if (wa->activate_or_deactivate && !wa->activate) { if (aux_state != NWAM_AUX_STATE_CONDITIONS_NOT_MET) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "deactivating NCU %s", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } } /* * If we are activating or checking the priority group and * too many exclusive NCUs are online, take this NCU down. */ if ((wa->activate_or_deactivate && wa->activate) || !wa->activate_or_deactivate) { if (state == NWAM_STATE_ONLINE && if_state == NWAM_STATE_ONLINE && wa->exclusive_online_ncus > 1) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "moving NCU %s to online* since another " "NCU is already active", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } } break; case NWAM_PRIORITY_MODE_SHARED: wa->shared_ncus++; if (state == NWAM_STATE_ONLINE && (if_state == NWAM_STATE_ONLINE || if_aux_state == NWAM_AUX_STATE_IF_WAITING_FOR_ADDR)) wa->shared_online_ncus++; if (wa->activate_or_deactivate && wa->activate) { if (state == NWAM_STATE_OFFLINE) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "activating NCU %s", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } } if (wa->activate_or_deactivate && !wa->activate) { if (aux_state != NWAM_AUX_STATE_CONDITIONS_NOT_MET) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "deactivating NCU %s", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } } break; case NWAM_PRIORITY_MODE_ALL: wa->all_ncus++; if (state == NWAM_STATE_ONLINE && (if_state == NWAM_STATE_ONLINE || if_aux_state == NWAM_AUX_STATE_IF_WAITING_FOR_ADDR)) wa->all_online_ncus++; /* * For "all" NCUs, activate/deactivate all offline/online * NCUs. */ if (wa->activate_or_deactivate && wa->activate) { if (state == NWAM_STATE_OFFLINE) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "activating NCU %s", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } } if (wa->activate_or_deactivate && !wa->activate) { if (aux_state != NWAM_AUX_STATE_CONDITIONS_NOT_MET) { nlog(LOG_DEBUG, "nwamd_ncu_check_or_activate: " "deactivating NCU %s", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } } break; default: nlog(LOG_ERR, "nwamd_ncu_check_or_activate: " "invalid priority-mode"); break; } return (0); } void nwamd_ncp_activate_priority_group(int64_t priority) { struct nwamd_ncu_check_walk_arg wa; nwamd_event_t check_event, priority_event; if (priority == INVALID_PRIORITY_GROUP) return; (void) pthread_mutex_lock(&active_ncp_mutex); if (priority == current_ncu_priority_group) { (void) pthread_mutex_unlock(&active_ncp_mutex); return; } (void) pthread_mutex_unlock(&active_ncp_mutex); nlog(LOG_DEBUG, "nwamd_ncp_activate_priority_group: " "activating priority group %lld", priority); wa.manual = B_FALSE; wa.priority_group = priority; wa.exclusive_ncus = 0; wa.exclusive_online_ncus = 0; wa.shared_ncus = 0; wa.shared_online_ncus = 0; wa.all_ncus = 0; wa.all_online_ncus = 0; wa.activate_or_deactivate = B_TRUE; wa.activate = B_TRUE; if (nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_check_or_activate, &wa) != 0) { nlog(LOG_ERR, "nwamd_ncp_activate_priority_group: " "nwamd_walk_objects() failed"); return; } /* * Enqueue event to update current_ncu_priority_group and send to * any event listeners. */ priority_event = nwamd_event_init_priority_group_change(priority); if (priority_event == NULL) return; nwamd_event_enqueue(priority_event); /* * Now we've activated a new priority group, enqueue an event * to check up on the state of this priority group. */ check_event = nwamd_event_init_ncu_check(); if (check_event == NULL) return; nwamd_event_enqueue_timed(check_event, ncu_wait_time); } void nwamd_ncp_deactivate_priority_group(int64_t priority) { struct nwamd_ncu_check_walk_arg wa; if (priority == INVALID_PRIORITY_GROUP) return; nlog(LOG_DEBUG, "nwamd_ncp_deactivate_priority_group: " "deactivating priority group %lld", priority); wa.manual = B_FALSE; wa.priority_group = priority; wa.exclusive_ncus = 0; wa.exclusive_online_ncus = 0; wa.shared_ncus = 0; wa.shared_online_ncus = 0; wa.all_ncus = 0; wa.all_online_ncus = 0; wa.activate_or_deactivate = B_TRUE; wa.activate = B_FALSE; if (nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_check_or_activate, &wa) != 0) { nlog(LOG_ERR, "nwamd_ncp_deactivate_priority_group: " "nwamd_walk_objects() failed"); return; } } /* * This function deactivates all priority groups at level 'priority' and lower * (which is, numerically, all priorities >= priority). */ void nwamd_ncp_deactivate_priority_group_all(int64_t priority) { if (priority == INVALID_PRIORITY_GROUP) return; nlog(LOG_DEBUG, "nwamd_ncp_deactivate_priority_group_all: " "deactivating priority group less than or equal to %lld", priority); do { nwamd_ncp_deactivate_priority_group(priority); } while (nwamd_ncp_find_next_priority_group(priority + 1, &priority)); } /* * Returns 'true' if it found the highest priority group no higher then what * is passed that should be activated and sets *priority to that. */ boolean_t nwamd_ncp_check_priority_group(int64_t *priority) { struct nwamd_ncu_check_walk_arg wa; boolean_t conditions_met = B_FALSE; nlog(LOG_DEBUG, "nwamd_ncp_check_priority_group: " "checking priority group %lld", *priority); if (*priority == INVALID_PRIORITY_GROUP) { if (!nwamd_ncp_find_next_priority_group(0, priority)) return (B_FALSE); } while (!conditions_met) { (void) memset(&wa, 0, sizeof (wa)); wa.manual = B_FALSE; wa.priority_group = *priority; wa.activate_or_deactivate = B_FALSE; if (nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_check_or_activate, &wa) != 0) { nlog(LOG_ERR, "nwamd_ncp_check_priority_group: " "nwamd_walk_objects() failed"); return (B_FALSE); } /* * Are activation conditons satisifed? In other words: * - exactly one of the exclusive NCUs is online * - 1 or more shared NCUs are online * - all of the all NCUs are online. * If any of these is untrue, conditions are not satisfied. */ conditions_met = B_TRUE; if (wa.exclusive_ncus > 0 && wa.exclusive_online_ncus != 1) conditions_met = B_FALSE; if (wa.shared_ncus > 0 && wa.shared_online_ncus == 0) conditions_met = B_FALSE; if (wa.all_ncus > 0 && wa.all_ncus != wa.all_online_ncus) conditions_met = B_FALSE; if (wa.exclusive_online_ncus == 0 && wa.shared_online_ncus == 0 && wa.all_online_ncus == 0) conditions_met = B_FALSE; if (conditions_met) { return (B_TRUE); } else { /* * If there is a next pg, activate it. If not, do * nothing - we're stuck here unless an event occurs * for our or a higher pg. */ if (!nwamd_ncp_find_next_priority_group (wa.priority_group + 1, priority)) { nlog(LOG_DEBUG, "ran out of prio groups"); return (B_FALSE); } } } return (B_FALSE); } void nwamd_ncp_activate_manual_ncus(void) { struct nwamd_ncu_check_walk_arg wa; nlog(LOG_DEBUG, "nwamd_ncp_activate_manual_ncus: activating NCUs"); wa.manual = B_TRUE; wa.activate_or_deactivate = B_TRUE; wa.activate = B_TRUE; if (nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_check_or_activate, &wa) != 0) { nlog(LOG_ERR, "nwamd_ncp_activate_manual_ncus: " "nwamd_walk_objects() failed"); return; } } void nwamd_create_ncu_check_event(uint64_t when) { nwamd_event_t check_event = nwamd_event_init_ncu_check(); if (check_event != NULL) nwamd_event_enqueue_timed(check_event, when); } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ #ifndef _NCP_H #define _NCP_H #include #include #include #include #include #include /* Time between NCU checks */ #define NCU_WAIT_TIME_DEFAULT 120 /* Value of priority-group at start and reset */ #define INVALID_PRIORITY_GROUP -1LL extern char active_ncp[]; extern nwam_ncp_handle_t active_ncph; extern int64_t current_ncu_priority_group; extern uint64_t ncu_wait_time; boolean_t nwamd_ncp_find_next_priority_group(int64_t, int64_t *); void nwamd_ncp_activate_priority_group(int64_t); void nwamd_ncp_deactivate_priority_group(int64_t); void nwamd_ncp_deactivate_priority_group_all(int64_t); boolean_t nwamd_ncp_check_priority_group(int64_t *); void nwamd_ncp_activate_manual_ncus(void); /* Create ncu check event */ void nwamd_create_ncu_check_event(uint64_t); #endif /* _NCP_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) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, Chris Fraire . */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "conditions.h" #include "events.h" #include "objects.h" #include "ncp.h" #include "util.h" /* * ncu.c - handles various NCU tasks - intialization/refresh, state machine * for NCUs etc. */ #define VBOX_IFACE_PREFIX "vboxnet" static void populate_ip_ncu_properties(nwam_ncu_handle_t, nwamd_ncu_t *); /* * Find ncu of specified type for link/interface name. */ nwamd_object_t nwamd_ncu_object_find(nwam_ncu_type_t type, const char *name) { nwam_error_t err; char *object_name; nwamd_object_t ncu_obj = NULL; if ((err = nwam_ncu_name_to_typed_name(name, type, &object_name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_find: nwam_ncu_name_to_typed_name " "returned %s", nwam_strerror(err)); return (NULL); } ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name); free(object_name); return (ncu_obj); } nwam_error_t nwamd_set_ncu_string(nwam_ncu_handle_t ncuh, char **strval, uint_t cnt, const char *prop) { nwam_error_t err; nwam_value_t val; if ((err = nwam_value_create_string_array(strval, cnt, &val)) != NWAM_SUCCESS) return (err); err = nwam_ncu_set_prop_value(ncuh, prop, val); nwam_value_free(val); return (err); } nwam_error_t nwamd_set_ncu_uint(nwam_ncu_handle_t ncuh, uint64_t *uintval, uint_t cnt, const char *prop) { nwam_error_t err; nwam_value_t val; if ((err = nwam_value_create_uint64_array(uintval, cnt, &val)) != NWAM_SUCCESS) return (err); err = nwam_ncu_set_prop_value(ncuh, prop, val); nwam_value_free(val); return (err); } nwam_error_t nwamd_get_ncu_string(nwam_ncu_handle_t ncuh, nwam_value_t *val, char ***strval, uint_t *cnt, const char *prop) { nwam_error_t err; if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS) return (err); return (nwam_value_get_string_array(*val, strval, cnt)); } nwam_error_t nwamd_get_ncu_uint(nwam_ncu_handle_t ncuh, nwam_value_t *val, uint64_t **uintval, uint_t *cnt, const char *prop) { nwam_error_t err; if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS) return (err); return (nwam_value_get_uint64_array(*val, uintval, cnt)); } nwam_error_t nwamd_get_ncu_boolean(nwam_ncu_handle_t ncuh, nwam_value_t *val, boolean_t **boolval, uint_t *cnt, const char *prop) { nwam_error_t err; if ((err = nwam_ncu_get_prop_value(ncuh, prop, val)) != NWAM_SUCCESS) return (err); return (nwam_value_get_boolean_array(*val, boolval, cnt)); } /* * Run link/interface state machine in response to a state change * or enable/disable action event. */ static void nwamd_ncu_state_machine(const char *object_name) { nwamd_object_t object; nwamd_ncu_t *ncu; link_state_t link_state; nwamd_event_t event; nwam_wlan_t key_wlan, connected_wlan; nwamd_link_t *link; char linkname[NWAM_MAX_NAME_LEN]; boolean_t up; if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, object_name)) == NULL) { nlog(LOG_ERR, "nwamd_ncu_state_machine: " "request for nonexistent NCU %s", object_name); return; } ncu = object->nwamd_object_data; link = &ncu->ncu_link; switch (object->nwamd_object_aux_state) { case NWAM_AUX_STATE_INITIALIZED: if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { /* * For wired/wireless links, need to get link * up/down events and even if these are not supported, * dlpi_open()ing the link prevents the driver from * being unloaded. */ nwamd_dlpi_add_link(object); if (link->nwamd_link_media == DL_WIFI) { /* * First, if we're unexpectedly connected, * disconnect. */ if (!link->nwamd_link_wifi_connected && nwamd_wlan_connected(object)) { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "WiFi unexpectedly connected, " "disconnecting..."); (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); } /* move to scanning aux state */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, object->nwamd_object_state, NWAM_AUX_STATE_LINK_WIFI_SCANNING); } else { /* * If initial wired link state is unknown, we * will need to assume the link is up, since * we won´t get DL_NOTE_LINK_UP/DOWN events. */ link_state = nwamd_get_link_state (ncu->ncu_name); if (link_state == LINK_STATE_UP || link_state == LINK_STATE_UNKNOWN) { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_ONLINE, NWAM_AUX_STATE_UP); } else { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); } } } else { /* * In the current implementation, initialization has to * start from scratch since the complexity of minimizing * configuration change is considerable (e.g. if we * refresh and had DHCP running on the physical * interface, and now have changed to static assignment, * we need to remove DHCP etc). To avoid all this, * unplumb before re-plumbing the protocols and * addresses we wish to configure. In the future, it * would be good to try and minimize configuration * changes. */ nwamd_unplumb_interface(ncu, AF_INET); nwamd_unplumb_interface(ncu, AF_INET6); /* * We may be restarting the state machine. Re-read * the IP NCU properties as the ipadm_addrobj_t in * nwamd_if_address should not be reused. */ populate_ip_ncu_properties(object->nwamd_object_handle, ncu); /* * Enqueue a WAITING_FOR_ADDR aux state change so that * we are eligible to receive the IF_STATE events * associated with static, DHCP, DHCPv6 and autoconf * address assignment. The latter two can happen * quite quickly after plumbing so we need to be ready. */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_IF_WAITING_FOR_ADDR); if (ncu->ncu_if.nwamd_if_ipv4) nwamd_plumb_interface(ncu, AF_INET); if (ncu->ncu_if.nwamd_if_ipv6) nwamd_plumb_interface(ncu, AF_INET6); /* Configure addresses */ nwamd_configure_interface_addresses(ncu); } break; case NWAM_AUX_STATE_IF_DHCP_TIMED_OUT: case NWAM_AUX_STATE_IF_WAITING_FOR_ADDR: /* * nothing to do here - RTM_NEWADDRs will trigger IF_STATE * events to move us online. */ break; case NWAM_AUX_STATE_LINK_WIFI_SCANNING: /* launch scan thread */ (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); (void) nwamd_wlan_scan(linkname); /* Create periodic scan event */ nwamd_ncu_create_periodic_scan_event(object); break; case NWAM_AUX_STATE_LINK_WIFI_NEED_SELECTION: /* send "need choice" event */ event = nwamd_event_init_wlan (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_CHOICE, B_FALSE, link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr, link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr_num); if (event == NULL) break; nwamd_event_enqueue(event); nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); break; case NWAM_AUX_STATE_LINK_WIFI_NEED_KEY: /* * Send "need key" event. Set selected to true, connected * and have_key to false. Do not fill in WLAN details as * multiple WLANs may match the ESSID name, and each may * have a different speed and channel. */ bzero(&key_wlan, sizeof (key_wlan)); (void) strlcpy(key_wlan.nww_essid, link->nwamd_link_wifi_essid, sizeof (key_wlan.nww_essid)); (void) strlcpy(key_wlan.nww_bssid, link->nwamd_link_wifi_bssid, sizeof (key_wlan.nww_bssid)); key_wlan.nww_security_mode = link->nwamd_link_wifi_security_mode; key_wlan.nww_selected = B_TRUE; key_wlan.nww_connected = B_FALSE; key_wlan.nww_have_key = B_FALSE; event = nwamd_event_init_wlan (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_NEED_KEY, B_FALSE, &key_wlan, 1); if (event == NULL) break; nwamd_event_enqueue(event); break; case NWAM_AUX_STATE_LINK_WIFI_CONNECTING: (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); nwamd_wlan_connect(linkname); break; case NWAM_AUX_STATE_UP: case NWAM_AUX_STATE_DOWN: up = (object->nwamd_object_aux_state == NWAM_AUX_STATE_UP); if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { if (link->nwamd_link_media == DL_WIFI) { /* * Connected/disconnected - send WLAN * connection report. */ link->nwamd_link_wifi_connected = up; nwamd_set_selected_connected(ncu, B_TRUE, up); (void) strlcpy(connected_wlan.nww_essid, link->nwamd_link_wifi_essid, sizeof (connected_wlan.nww_essid)); (void) strlcpy(connected_wlan.nww_bssid, link->nwamd_link_wifi_bssid, sizeof (connected_wlan.nww_bssid)); connected_wlan.nww_security_mode = link->nwamd_link_wifi_security_mode; event = nwamd_event_init_wlan (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_CONNECTION_REPORT, up, &connected_wlan, 1); if (event == NULL) break; nwamd_event_enqueue(event); /* * If disconnected, restart the state machine * for the WiFi link (WiFi is always trying * to connect). * * If connected, start signal strength * monitoring thread. */ if (!up && ncu->ncu_enabled) { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "wifi disconnect - start over " "after %dsec interval", WIRELESS_RETRY_INTERVAL); link->nwamd_link_wifi_connected = B_FALSE; /* propogate down event to IP NCU */ nwamd_propogate_link_up_down_to_ip (ncu->ncu_name, B_FALSE); nwamd_object_set_state_timed (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED, WIRELESS_RETRY_INTERVAL); } else { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "wifi connected, start monitoring"); (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); nwamd_wlan_monitor_signal(linkname); } } } /* If not in ONLINE/OFFLINE state yet, change state */ if ((up && object->nwamd_object_state != NWAM_STATE_ONLINE) || (!up && object->nwamd_object_state != NWAM_STATE_OFFLINE)) { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "%s is moving %s", object_name, up ? "online" : "offline"); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, up ? NWAM_STATE_ONLINE : NWAM_STATE_OFFLINE, up ? NWAM_AUX_STATE_UP : NWAM_AUX_STATE_DOWN); if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) { if (up) { /* * Moving online, add v4/v6 default * routes (if any). */ nwamd_add_default_routes(ncu); } else { /* * If this is an interface NCU and we * got a down event, it is a consequence * of NCU refresh, so reapply addresses * by reinitializing. */ nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } } } else { nlog(LOG_DEBUG, "nwamd_ncu_state_machine: " "%s is %s", object_name, up ? "online" : "offline"); } /* * NCU is UP or DOWN, trigger all condition checking, even if * the NCU is already in the ONLINE state - an ENM may depend * on NCU activity. */ nwamd_create_triggered_condition_check_event(NEXT_FEW_SECONDS); break; case NWAM_AUX_STATE_CONDITIONS_NOT_MET: /* * Link/interface is moving offline. Nothing to do except * for WiFi, where we disconnect. Don't unplumb IP on * a link since it may be a transient change. */ if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { if (link->nwamd_link_media == DL_WIFI) { (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); link->nwamd_link_wifi_connected = B_FALSE; nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); } } else { /* * Unplumb here. In the future we may elaborate on * the approach used and not unplumb for WiFi * until we reconnect to a different WLAN (i.e. with * a different ESSID). */ nwamd_unplumb_interface(ncu, AF_INET); nwamd_unplumb_interface(ncu, AF_INET6); } if (object->nwamd_object_state != NWAM_STATE_OFFLINE) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } break; case NWAM_AUX_STATE_MANUAL_DISABLE: /* Manual disable, set enabled state appropriately. */ ncu->ncu_enabled = B_FALSE; /* FALLTHROUGH */ case NWAM_AUX_STATE_UNINITIALIZED: case NWAM_AUX_STATE_NOT_FOUND: /* * Link/interface NCU has been disabled/deactivated/removed. * For WiFi links disconnect, and for IP interfaces we unplumb. */ if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { if (link->nwamd_link_media == DL_WIFI) { (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); link->nwamd_link_wifi_connected = B_FALSE; nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); } nwamd_dlpi_delete_link(object); } else { /* Unplumb here. */ if (ncu->ncu_if.nwamd_if_ipv4) { nwamd_unplumb_interface(ncu, AF_INET); } if (ncu->ncu_if.nwamd_if_ipv6) { nwamd_unplumb_interface(ncu, AF_INET6); } /* trigger location condition checking */ nwamd_create_triggered_condition_check_event(0); } switch (object->nwamd_object_aux_state) { case NWAM_AUX_STATE_MANUAL_DISABLE: /* Change state to DISABLED if manually disabled */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_DISABLED, NWAM_AUX_STATE_MANUAL_DISABLE); /* Note that NCU has been disabled */ ncu->ncu_enabled = B_FALSE; break; case NWAM_AUX_STATE_NOT_FOUND: /* Change state to UNINITIALIZED for device removal */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_UNINITIALIZED, NWAM_AUX_STATE_NOT_FOUND); break; default: break; } break; default: nlog(LOG_ERR, "nwamd_ncu_state_machine: unexpected state"); break; } nwamd_object_release(object); } static int ncu_create_init_fini_event(nwam_ncu_handle_t ncuh, void *data) { boolean_t *init = data; char *name, *typedname; nwam_error_t err; nwam_value_t typeval = NULL; uint64_t *type; uint_t numvalues; nwamd_event_t ncu_event; if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) { nlog(LOG_ERR, "ncu_create_init_fini_event: could not get NCU name"); return (0); } nlog(LOG_DEBUG, "ncu_create_init_fini_event(%s, %p)", name, data); if ((err = nwamd_get_ncu_uint(ncuh, &typeval, &type, &numvalues, NWAM_NCU_PROP_TYPE)) != NWAM_SUCCESS) { nlog(LOG_ERR, "ncu_create_init_fini_event: " "could not get NCU type: %s", nwam_strerror(err)); free(name); nwam_value_free(typeval); return (0); } /* convert name to typedname for event */ if ((err = nwam_ncu_name_to_typed_name(name, *type, &typedname)) != NWAM_SUCCESS) { nlog(LOG_ERR, "ncu_create_init_fini_event: " "NCU name translation failed: %s", nwam_strerror(err)); free(name); return (0); } free(name); nwam_value_free(typeval); ncu_event = nwamd_event_init(*init ? NWAM_EVENT_TYPE_OBJECT_INIT : NWAM_EVENT_TYPE_OBJECT_FINI, NWAM_OBJECT_TYPE_NCU, 0, typedname); if (ncu_event != NULL) nwamd_event_enqueue(ncu_event); free(typedname); return (0); } /* * Initialization - walk the NCUs, creating initialization events for each * NCU. nwamd_ncu_handle_init_event() will check if the associated * physical link exists or not. */ void nwamd_init_ncus(void) { boolean_t init = B_TRUE; (void) pthread_mutex_lock(&active_ncp_mutex); if (active_ncph != NULL) { nlog(LOG_DEBUG, "nwamd_init_ncus: " "(re)intializing NCUs for NCP %s", active_ncp); (void) nwam_ncp_walk_ncus(active_ncph, ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL, NULL); } (void) pthread_mutex_unlock(&active_ncp_mutex); } void nwamd_fini_ncus(void) { boolean_t init = B_FALSE; /* We may not have an active NCP on initialization, so skip fini */ (void) pthread_mutex_lock(&active_ncp_mutex); if (active_ncph != NULL) { nlog(LOG_DEBUG, "nwamd_fini_ncus: deinitializing NCUs for %s", active_ncp); (void) nwam_ncp_walk_ncus(active_ncph, ncu_create_init_fini_event, &init, NWAM_FLAG_NCU_TYPE_ALL, NULL); } (void) pthread_mutex_unlock(&active_ncp_mutex); } /* * Most properties of this type don't need to be cached locally. Only those * interesting to the daemon are stored in an nwamd_ncu_t. */ static void populate_common_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data) { nwam_value_t ncu_prop; nwam_error_t err; boolean_t enablevalue; uint_t numvalues; char **parent; if ((err = nwam_ncu_get_prop_value(ncuh, NWAM_NCU_PROP_ENABLED, &ncu_prop)) != NWAM_SUCCESS) { char *name; (void) nwam_ncu_name_to_typed_name(ncu_data->ncu_name, ncu_data->ncu_type, &name); nlog(LOG_ERR, "nwam_ncu_get_prop_value %s ENABLED failed: %s", name, nwam_strerror(err)); free(name); ncu_data->ncu_enabled = B_TRUE; } else { if ((err = nwam_value_get_boolean(ncu_prop, &enablevalue)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwam_value_get_boolean ENABLED failed: " "%s", nwam_strerror(err)); } else { ncu_data->ncu_enabled = enablevalue; } nwam_value_free(ncu_prop); } if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &parent, &numvalues, NWAM_NCU_PROP_PARENT_NCP)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwam_ncu_get_prop_value %s PARENT failed: %s", ncu_data->ncu_name, nwam_strerror(err)); } else { (void) strlcpy(ncu_data->ncu_parent, parent[0], sizeof (ncu_data->ncu_parent)); nwam_value_free(ncu_prop); } } /* * Read in link properties. */ static void populate_link_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data) { nwam_value_t ncu_prop; nwam_error_t err; char **mac_addr; uint64_t *uintval; uint_t numvalues; /* activation-mode */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_ACTIVATION_MODE, nwam_strerror(err)); } else { ncu_data->ncu_link.nwamd_link_activation_mode = uintval[0]; nwam_value_free(ncu_prop); } /* priority-group and priority-mode for prioritized activation */ if (ncu_data->ncu_link.nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED) { /* ncus with prioritized activation are always enabled */ ncu_data->ncu_enabled = B_TRUE; if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_PRIORITY_MODE)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_link_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_PRIORITY_MODE, nwam_strerror(err)); } else { ncu_data->ncu_link.nwamd_link_priority_mode = uintval[0]; nwam_value_free(ncu_prop); } if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_PRIORITY_GROUP)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_link_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_PRIORITY_GROUP, nwam_strerror(err)); } else { ncu_data->ncu_link.nwamd_link_priority_group = uintval[0]; nwam_value_free(ncu_prop); } } /* link-mac-addr */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &mac_addr, &numvalues, NWAM_NCU_PROP_LINK_MAC_ADDR)) != NWAM_SUCCESS) { nlog(LOG_DEBUG, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_LINK_MAC_ADDR, nwam_strerror(err)); ncu_data->ncu_link.nwamd_link_mac_addr = NULL; } else { ncu_data->ncu_link.nwamd_link_mac_addr = strdup(*mac_addr); ncu_data->ncu_link.nwamd_link_mac_addr_len = strlen(*mac_addr); nwam_value_free(ncu_prop); } /* link-mtu */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &uintval, &numvalues, NWAM_NCU_PROP_LINK_MTU)) != NWAM_SUCCESS) { nlog(LOG_DEBUG, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_LINK_MTU, nwam_strerror(err)); ncu_data->ncu_link.nwamd_link_mtu = 0; } else { ncu_data->ncu_link.nwamd_link_mtu = uintval[0]; nwam_value_free(ncu_prop); } /* link-autopush */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &ncu_data->ncu_link.nwamd_link_autopush, &ncu_data->ncu_link.nwamd_link_num_autopush, NWAM_NCU_PROP_LINK_AUTOPUSH)) != NWAM_SUCCESS) { nlog(LOG_DEBUG, "populate_link_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_LINK_AUTOPUSH, nwam_strerror(err)); ncu_data->ncu_link.nwamd_link_num_autopush = 0; } } static void populate_ip_ncu_properties(nwam_ncu_handle_t ncuh, nwamd_ncu_t *ncu_data) { nwamd_if_t *nif = &ncu_data->ncu_if; struct nwamd_if_address **nifa, *nifai, *nifait; boolean_t static_addr = B_FALSE, *boolvalue, dhcp_primary = B_FALSE; uint64_t *addrsrcvalue; nwam_value_t ncu_prop; nwam_error_t err; ipadm_addrobj_t ipaddr; ipadm_status_t ipstatus; char **addrvalue, ipreqhost[MAXNAMELEN]; uint_t numvalues; uint64_t *ipversion; int i; nif->nwamd_if_ipv4 = B_FALSE; nif->nwamd_if_ipv6 = B_FALSE; nif->nwamd_if_dhcp_requested = B_FALSE; nif->nwamd_if_stateful_requested = B_FALSE; nif->nwamd_if_stateless_requested = B_FALSE; nif->nwamd_if_ipv4_default_route_set = B_FALSE; nif->nwamd_if_ipv6_default_route_set = B_FALSE; /* ip-version */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &ipversion, &numvalues, NWAM_NCU_PROP_IP_VERSION)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_IP_VERSION, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { switch (ipversion[i]) { case IPV4_VERSION: nif->nwamd_if_ipv4 = B_TRUE; break; case IPV6_VERSION: nif->nwamd_if_ipv6 = B_TRUE; break; default: nlog(LOG_ERR, "bogus ip version %lld", ipversion[i]); break; } } nwam_value_free(ncu_prop); } /* ip-primary */ if ((err = nwamd_get_ncu_boolean(ncuh, &ncu_prop, &boolvalue, &numvalues, NWAM_NCU_PROP_IP_PRIMARY)) != NWAM_SUCCESS) { /* ip-primary is optional, so do not LOG_ERR */ nlog(LOG_DEBUG, "populate_ip_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_IP_PRIMARY, nwam_strerror(err)); } else { if (numvalues > 0) dhcp_primary = boolvalue[0]; nwam_value_free(ncu_prop); } /* ip-reqhost */ *ipreqhost = '\0'; if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IP_REQHOST)) != NWAM_SUCCESS) { /* ip-reqhost is optional, so do not LOG_ERR */ nlog(LOG_DEBUG, "populate_ip_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_IP_REQHOST, nwam_strerror(err)); } else { if (numvalues > 0 && strlcpy(ipreqhost, addrvalue[0], sizeof (ipreqhost)) >= sizeof (ipreqhost)) { nlog(LOG_WARNING, "populate_ip_ncu_properties: " "too long %s value: %s", NWAM_NCU_PROP_IP_REQHOST, addrvalue[0]); *ipreqhost = '\0'; } nwam_value_free(ncu_prop); } /* Free the old list. */ for (nifai = nif->nwamd_if_list; nifai != NULL; nifai = nifait) { nifait = nifai->next; nifai->next = NULL; ipadm_destroy_addrobj(nifai->ipaddr); free(nifai); } nif->nwamd_if_list = NULL; nifa = &(nif->nwamd_if_list); if (!nif->nwamd_if_ipv4) goto skip_ipv4; /* ipv4-addrsrc */ if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue, &numvalues, NWAM_NCU_PROP_IPV4_ADDRSRC)) != NWAM_SUCCESS) { nlog(nif->nwamd_if_ipv4 ? LOG_ERR : LOG_DEBUG, "populate_ip_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_IPV4_ADDRSRC, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { switch (addrsrcvalue[i]) { case NWAM_ADDRSRC_DHCP: nif->nwamd_if_dhcp_requested = B_TRUE; break; case NWAM_ADDRSRC_STATIC: static_addr = B_TRUE; break; default: break; } } nwam_value_free(ncu_prop); } if (nif->nwamd_if_dhcp_requested) { ipstatus = ipadm_create_addrobj(IPADM_ADDR_DHCP, ncu_data->ncu_name, &ipaddr); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_create_addrobj failed for v4 dhcp: %s", ipadm_status2str(ipstatus)); goto skip_ipv4_dhcp; } ipstatus = ipadm_set_wait_time(ipaddr, ncu_wait_time); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_set_wait_time failed for v4 dhcp: %s", ipadm_status2str(ipstatus)); ipadm_destroy_addrobj(ipaddr); goto skip_ipv4_dhcp; } ipstatus = ipadm_set_primary(ipaddr, dhcp_primary); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_set_primary failed for v4 dhcp: %s", ipadm_status2str(ipstatus)); ipadm_destroy_addrobj(ipaddr); goto skip_ipv4_dhcp; } ipstatus = ipadm_set_reqhost(ipaddr, ipreqhost); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_set_reqhost failed for v4 dhcp: %s", ipadm_status2str(ipstatus)); ipadm_destroy_addrobj(ipaddr); goto skip_ipv4_dhcp; } if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) { (*nifa)->family = AF_INET; (*nifa)->ipaddr_atype = IPADM_ADDR_DHCP; (*nifa)->ipaddr = ipaddr; nifa = &((*nifa)->next); *nifa = NULL; } else { nlog(LOG_ERR, "populate_ip_ncu_properties: " "couldn't allocate nwamd address for v4 dhcp: %s", strerror(errno)); ipadm_destroy_addrobj(ipaddr); } } skip_ipv4_dhcp: /* ipv4-addr */ if (static_addr) { if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV4_ADDR)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_IPV4_ADDR, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { ipstatus = ipadm_create_addrobj( IPADM_ADDR_STATIC, ncu_data->ncu_name, &ipaddr); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_create_addrobj failed " "for %s: %s", addrvalue[i], ipadm_status2str(ipstatus)); continue; } /* ipadm_set_addr takes [/] */ ipstatus = ipadm_set_addr(ipaddr, addrvalue[i], AF_INET); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_set_addr failed for %s: %s", addrvalue[i], ipadm_status2str(ipstatus)); ipadm_destroy_addrobj(ipaddr); continue; } if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) { (*nifa)->family = AF_INET; (*nifa)->ipaddr_atype = IPADM_ADDR_STATIC; (*nifa)->ipaddr = ipaddr; nifa = &((*nifa)->next); } else { nlog(LOG_ERR, "populate_ip_ncu_properties: " "couldn't allocate nwamd address " "for %s: %s", addrvalue[i], strerror(errno)); ipadm_destroy_addrobj(ipaddr); } } *nifa = NULL; nwam_value_free(ncu_prop); } } /* get default route, if any */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV4_DEFAULT_ROUTE)) == NWAM_SUCCESS) { /* Only one default route is allowed. */ nif->nwamd_if_ipv4_default_route.sin_family = AF_INET; (void) inet_pton(AF_INET, addrvalue[0], &(nif->nwamd_if_ipv4_default_route.sin_addr)); nif->nwamd_if_ipv4_default_route_set = B_TRUE; nwam_value_free(ncu_prop); } skip_ipv4: if (!nif->nwamd_if_ipv6) goto skip_ipv6; /* ipv6-addrsrc */ static_addr = B_FALSE; if ((err = nwamd_get_ncu_uint(ncuh, &ncu_prop, &addrsrcvalue, &numvalues, NWAM_NCU_PROP_IPV6_ADDRSRC)) != NWAM_SUCCESS) { nlog(nif->nwamd_if_ipv6 ? LOG_ERR : LOG_DEBUG, "populate_ip_ncu_properties: could not get %s value: %s", NWAM_NCU_PROP_IPV6_ADDRSRC, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { switch (addrsrcvalue[i]) { case NWAM_ADDRSRC_DHCP: nif->nwamd_if_stateful_requested = B_TRUE; break; case NWAM_ADDRSRC_AUTOCONF: nif->nwamd_if_stateless_requested = B_TRUE; break; case NWAM_ADDRSRC_STATIC: static_addr = B_TRUE; break; default: break; } } nwam_value_free(ncu_prop); } /* * Both stateful and stateless share the same nwamd_if_address because * only one ipaddr for both of these addresses can be created. * ipadm_create_addr() adds both addresses from the same ipaddr. */ if (nif->nwamd_if_stateful_requested || nif->nwamd_if_stateless_requested) { ipstatus = ipadm_create_addrobj(IPADM_ADDR_IPV6_ADDRCONF, ncu_data->ncu_name, &ipaddr); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_create_addrobj failed for v6 " "stateless/stateful: %s", ipadm_status2str(ipstatus)); goto skip_ipv6_addrconf; } /* create_addrobj sets both stateless and stateful to B_TRUE */ if (!nif->nwamd_if_stateful_requested) { ipstatus = ipadm_set_stateful(ipaddr, B_FALSE); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_set_stateful failed for v6: %s", ipadm_status2str(ipstatus)); ipadm_destroy_addrobj(ipaddr); goto skip_ipv6_addrconf; } } if (!nif->nwamd_if_stateless_requested) { ipstatus = ipadm_set_stateless(ipaddr, B_FALSE); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_set_stateless failed for v6: %s", ipadm_status2str(ipstatus)); ipadm_destroy_addrobj(ipaddr); goto skip_ipv6_addrconf; } } if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) { (*nifa)->family = AF_INET6; (*nifa)->ipaddr_atype = IPADM_ADDR_IPV6_ADDRCONF; (*nifa)->ipaddr = ipaddr; nifa = &((*nifa)->next); *nifa = NULL; } else { nlog(LOG_ERR, "populate_ip_ncu_properties: " "couldn't allocate nwamd address for " "v6 stateless/stateful: %s", strerror(errno)); ipadm_destroy_addrobj(ipaddr); } } skip_ipv6_addrconf: /* ipv6-addr */ if (static_addr) { if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV6_ADDR)) != NWAM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "could not get %s value: %s", NWAM_NCU_PROP_IPV6_ADDR, nwam_strerror(err)); } else { for (i = 0; i < numvalues; i++) { ipstatus = ipadm_create_addrobj( IPADM_ADDR_STATIC, ncu_data->ncu_name, &ipaddr); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_create_addrobj failed " "for %s: %s", addrvalue[i], ipadm_status2str(ipstatus)); continue; } /* ipadm_set_addr takes [/] */ ipstatus = ipadm_set_addr(ipaddr, addrvalue[i], AF_INET6); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "populate_ip_ncu_properties: " "ipadm_set_addr failed for %s: %s", addrvalue[i], ipadm_status2str(ipstatus)); ipadm_destroy_addrobj(ipaddr); continue; } if ((*nifa = calloc(sizeof (**nifa), 1)) != NULL) { (*nifa)->family = AF_INET6; (*nifa)->ipaddr_atype = IPADM_ADDR_STATIC; (*nifa)->ipaddr = ipaddr; nifa = &((*nifa)->next); } else { nlog(LOG_ERR, "populate_ip_ncu_properties: " "couldn't allocate nwamd address " "for %s: %s", addrvalue[i], strerror(errno)); ipadm_destroy_addrobj(ipaddr); } } *nifa = NULL; nwam_value_free(ncu_prop); } } /* get default route, if any */ if ((err = nwamd_get_ncu_string(ncuh, &ncu_prop, &addrvalue, &numvalues, NWAM_NCU_PROP_IPV6_DEFAULT_ROUTE)) == NWAM_SUCCESS) { /* Only one default route is allowed. */ nif->nwamd_if_ipv6_default_route.sin6_family = AF_INET6; (void) inet_pton(AF_INET6, addrvalue[0], &(nif->nwamd_if_ipv6_default_route.sin6_addr)); nif->nwamd_if_ipv6_default_route_set = B_TRUE; nwam_value_free(ncu_prop); } skip_ipv6: ; } static nwamd_ncu_t * nwamd_ncu_init(nwam_ncu_type_t ncu_type, const char *name) { nwamd_ncu_t *rv; nlog(LOG_DEBUG, "nwamd_ncu_init(%d, %s)", ncu_type, name); if ((rv = calloc(1, sizeof (*rv))) == NULL) return (NULL); rv->ncu_type = ncu_type; rv->ncu_name = strdup(name); rv->ncu_enabled = B_FALSE; /* Initialize link/interface-specific data */ if (rv->ncu_type == NWAM_NCU_TYPE_LINK) { (void) bzero(&rv->ncu_link, sizeof (nwamd_link_t)); (void) dladm_name2info(dld_handle, name, &rv->ncu_link.nwamd_link_id, NULL, NULL, &rv->ncu_link.nwamd_link_media); (void) pthread_mutex_init( &rv->ncu_link.nwamd_link_wifi_mutex, NULL); rv->ncu_link.nwamd_link_wifi_priority = MAXINT; } else { (void) bzero(&rv->ncu_if, sizeof (nwamd_if_t)); } return (rv); } void nwamd_ncu_free(nwamd_ncu_t *ncu) { if (ncu != NULL) { assert(ncu->ncu_type == NWAM_NCU_TYPE_LINK || ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE); if (ncu->ncu_type == NWAM_NCU_TYPE_LINK) { struct nwamd_link *l = &ncu->ncu_link; int i; free(l->nwamd_link_wifi_key); free(l->nwamd_link_mac_addr); for (i = 0; i < l->nwamd_link_num_autopush; i++) free(l->nwamd_link_autopush[i]); } else if (ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE) { struct nwamd_if_address *nifa; nifa = ncu->ncu_if.nwamd_if_list; while (nifa != NULL) { struct nwamd_if_address *n; n = nifa; nifa = nifa->next; ipadm_destroy_addrobj(n->ipaddr); free(n); } } free(ncu->ncu_name); free(ncu); } } static int nwamd_ncu_display(nwamd_object_t ncu_obj, void *data) { nwamd_ncu_t *ncu = (nwamd_ncu_t *)ncu_obj->nwamd_object_data; data = data; nlog(LOG_DEBUG, "NCU (%p) %s state %s, %s", (void *)ncu, ncu_obj->nwamd_object_name, nwam_state_to_string(ncu_obj->nwamd_object_state), nwam_aux_state_to_string(ncu_obj->nwamd_object_aux_state)); return (0); } void nwamd_log_ncus(void) { nlog(LOG_DEBUG, "NCP %s", active_ncp); (void) nwamd_walk_objects(NWAM_OBJECT_TYPE_NCU, nwamd_ncu_display, NULL); } int nwamd_ncu_action(const char *ncu, const char *parent, nwam_action_t action) { nwamd_event_t ncu_event = nwamd_event_init_object_action (NWAM_OBJECT_TYPE_NCU, ncu, parent, action); if (ncu_event == NULL) return (1); nwamd_event_enqueue(ncu_event); return (0); } static void add_phys_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name) { dladm_status_t dlrtn; uint32_t media; boolean_t is_wireless; nwam_error_t err; nwam_ncu_handle_t ncuh; uint64_t uintval; if ((dlrtn = dladm_name2info(dld_handle, name, NULL, NULL, NULL, &media)) != DLADM_STATUS_OK) { char errmsg[DLADM_STRSIZE]; nlog(LOG_ERR, "failed to get media type for %s: %s", name, dladm_status2str(dlrtn, errmsg)); return; } is_wireless = (media == DL_WIFI); if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_LINK, NWAM_NCU_CLASS_PHYS, &ncuh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create link ncu for %s: %s", name, nwam_strerror(err)); if (err == NWAM_ENTITY_READ_ONLY) { nwamd_event_t retry_event; /* * Root filesystem may be read-only, retry in * a few seconds. */ nlog(LOG_DEBUG, "Retrying addition of phys ncu for %s", name); retry_event = nwamd_event_init_link_action(name, NWAM_ACTION_ADD); if (retry_event != NULL) { nwamd_event_enqueue_timed(retry_event, NWAMD_READONLY_RETRY_INTERVAL); } } return; } uintval = NWAM_ACTIVATION_MODE_PRIORITIZED; if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1, NWAM_NCU_PROP_ACTIVATION_MODE)) != NWAM_SUCCESS) { goto finish; } uintval = is_wireless ? 1 : 0; if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1, NWAM_NCU_PROP_PRIORITY_GROUP)) != NWAM_SUCCESS) { goto finish; } uintval = is_wireless ? NWAM_PRIORITY_MODE_EXCLUSIVE : NWAM_PRIORITY_MODE_SHARED; if ((err = nwamd_set_ncu_uint(ncuh, &uintval, 1, NWAM_NCU_PROP_PRIORITY_MODE)) != NWAM_SUCCESS) { goto finish; } err = nwam_ncu_commit(ncuh, 0); finish: nwam_ncu_free(ncuh); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create automatic link ncu for %s: %s", name, nwam_strerror(err)); } } static void add_ip_ncu_to_ncp(nwam_ncp_handle_t ncph, const char *name) { nwam_error_t err; nwam_ncu_handle_t ncuh; if ((err = nwam_ncu_create(ncph, name, NWAM_NCU_TYPE_INTERFACE, NWAM_NCU_CLASS_IP, &ncuh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create ip ncu for %s: %s", name, nwam_strerror(err)); /* * Root filesystem may be read-only, but no need to * retry here since add_phys_ncu_to_ncp() enqueues * a retry event which will lead to add_ip_ncu_to_ncp() * being called. */ return; } /* IP NCU has the default values, so nothing else to do */ err = nwam_ncu_commit(ncuh, 0); nwam_ncu_free(ncuh); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to create ip ncu for %s: %s", name, nwam_strerror(err)); } } static void remove_ncu_from_ncp(nwam_ncp_handle_t ncph, const char *name, nwam_ncu_type_t type) { nwam_error_t err; nwam_ncu_handle_t ncuh; if ((err = nwam_ncu_read(ncph, name, type, 0, &ncuh)) != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to read automatic ncu %s: %s", name, nwam_strerror(err)); return; } err = nwam_ncu_destroy(ncuh, 0); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "failed to delete automatic ncu %s: %s", name, nwam_strerror(err)); } } /* * Device represented by NCU has been added or removed for the active * User NCP. If an associated NCU of the given type is found, transition it * to the appropriate state. */ void ncu_action_change_state(nwam_action_t action, nwam_ncu_type_t type, const char *name) { nwamd_object_t ncu_obj = NULL; nwamd_ncu_t *ncu; if ((ncu_obj = nwamd_ncu_object_find(type, name)) == NULL) return; ncu = ncu_obj->nwamd_object_data; /* * If device has been added, transition from uninitialized to offline. * If device has been removed, transition to uninitialized (via online* * if the NCU is currently enabled in order to tear down config). */ if (action == NWAM_ACTION_ADD) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE, NWAM_AUX_STATE_CONDITIONS_NOT_MET); } else { if (ncu->ncu_enabled) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_NOT_FOUND); } else { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_UNINITIALIZED, NWAM_AUX_STATE_NOT_FOUND); } } nwamd_object_release(ncu_obj); } /* * Called with hotplug sysevent or when nwam is started and walking the * physical interfaces. Add/remove both link and interface NCUs from the * Automatic NCP. Assumes that both link and interface NCUs don't exist. */ void nwamd_ncu_handle_link_action_event(nwamd_event_t event) { nwam_ncp_handle_t ncph; nwam_ncu_type_t type; nwam_action_t action = event->event_msg->nwe_data.nwe_link_action.nwe_action; nwam_error_t err; char *name; boolean_t automatic_ncp_active = B_FALSE; if (action != NWAM_ACTION_ADD && action != NWAM_ACTION_REMOVE) { nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "invalid link action %s", nwam_action_to_string(action)); nwamd_event_do_not_send(event); return; } nlog(LOG_DEBUG, "nwamd_ncu_handle_link_action_event: " "link action '%s' event on %s", nwam_action_to_string(action), event->event_object[0] == 0 ? "n/a" : event->event_object); if ((err = nwam_ncu_typed_name_to_name(event->event_object, &type, &name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "translation from typedname error: %s", nwam_strerror(err)); nwamd_event_do_not_send(event); return; } (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 && active_ncph != NULL) { automatic_ncp_active = B_TRUE; } (void) pthread_mutex_unlock(&active_ncp_mutex); /* * We could use active_ncph for cases where the Automatic NCP is active, * but that would involve holding the active_ncp_mutex for too long. */ if ((err = nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph)) == NWAM_ENTITY_NOT_FOUND) { /* Automatic NCP doesn't exist, create it */ err = nwam_ncp_create(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph); } if (err != NWAM_SUCCESS) goto fail; /* add or remove NCUs from Automatic NCP */ if (action == NWAM_ACTION_ADD) { add_phys_ncu_to_ncp(ncph, name); add_ip_ncu_to_ncp(ncph, name); } else { /* * Order is important here, remove IP NCU first to prevent * propogation of down event from link to IP. No need to * create REFRESH or DESTROY events. They are generated by * nwam_ncu_commit() and nwam_ncu_destroy(). */ remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_INTERFACE); remove_ncu_from_ncp(ncph, name, NWAM_NCU_TYPE_LINK); } nwam_ncp_free(ncph); /* * If the Automatic NCP is not active, and the associated NCUs * exist, they must be moved into the appropriate states given the * action that has occurred. */ if (!automatic_ncp_active) { ncu_action_change_state(action, NWAM_NCU_TYPE_INTERFACE, name); ncu_action_change_state(action, NWAM_NCU_TYPE_LINK, name); } /* Need NCU check to evaluate state in light of added/removed NCUs */ if (!nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK, NWAM_OBJECT_TYPE_NCP, NULL)) { nwamd_create_ncu_check_event(NEXT_FEW_SECONDS); } fail: free(name); if (err != NWAM_SUCCESS) { nwamd_event_t retry_event = nwamd_event_init_link_action(name, action); if (retry_event == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "could not create retry event to read/create " "%s NCP", NWAM_NCP_NAME_AUTOMATIC); return; } nlog(LOG_ERR, "nwamd_ncu_handle_link_action_event: " "could not read/create %s NCP, retrying in %d seconds", NWAM_NCP_NAME_AUTOMATIC, NWAMD_READONLY_RETRY_INTERVAL); nwamd_event_enqueue_timed(retry_event, NWAMD_READONLY_RETRY_INTERVAL); } } /* * Figure out if this link is part of an aggregation. This is fairly * inefficient since we generate this list for every query and search * linearly. A better way would be to generate the list of links in an * aggregation once and then check each link against it. */ struct link_aggr_search_data { datalink_id_t linkid; boolean_t under; }; static int ncu_aggr_search(const char *name, void *data) { struct link_aggr_search_data *lasd = data; dladm_aggr_grp_attr_t ginfo; datalink_id_t linkid; int i; if (dladm_name2info(dld_handle, name, &linkid, NULL, NULL, NULL) != DLADM_STATUS_OK) return (DLADM_WALK_CONTINUE); if (dladm_aggr_info(dld_handle, linkid, &ginfo, DLADM_OPT_ACTIVE) != DLADM_STATUS_OK || ginfo.lg_nports == 0) return (DLADM_WALK_CONTINUE); for (i = 0; i < ginfo.lg_nports; i++) { if (lasd->linkid == ginfo.lg_ports[i].lp_linkid) { lasd->under = B_TRUE; return (DLADM_WALK_TERMINATE); } } free(ginfo.lg_ports); return (DLADM_WALK_CONTINUE); } static boolean_t nwamd_link_belongs_to_an_aggr(const char *name) { struct link_aggr_search_data lasd; if (dladm_name2info(dld_handle, name, &lasd.linkid, NULL, NULL, NULL) != DLADM_STATUS_OK) return (B_FALSE); lasd.under = B_FALSE; (void) dladm_walk(ncu_aggr_search, dld_handle, &lasd, DATALINK_CLASS_AGGR, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); return (lasd.under); } /* * If NCU doesn't exist for interface with given name, enqueue a ADD * LINK_ACTION event. */ static int ncu_create_link_action_event(const char *name, void *data) { nwam_ncp_handle_t ncph = data; nwam_ncu_handle_t ncuh; nwamd_event_t link_event; /* Do not generate an event if this is a VirtualBox interface. */ if (strncmp(name, VBOX_IFACE_PREFIX, strlen(VBOX_IFACE_PREFIX)) == 0) return (DLADM_WALK_CONTINUE); /* Do not generate an event if this link belongs to another zone. */ if (!nwamd_link_belongs_to_this_zone(name)) return (DLADM_WALK_CONTINUE); /* Do not generate an event if this link belongs to an aggregation. */ if (nwamd_link_belongs_to_an_aggr(name)) { return (DLADM_WALK_CONTINUE); } /* Don't create an event if the NCU already exists. */ if (ncph != NULL && nwam_ncu_read(ncph, name, NWAM_NCU_TYPE_LINK, 0, &ncuh) == NWAM_SUCCESS) { nwam_ncu_free(ncuh); return (DLADM_WALK_CONTINUE); } nlog(LOG_DEBUG, "ncu_create_link_action_event: adding ncus for %s", name); link_event = nwamd_event_init_link_action(name, NWAM_ACTION_ADD); if (link_event != NULL) nwamd_event_enqueue(link_event); return (DLADM_WALK_CONTINUE); } /* * Check if interface exists for this NCU. If not, enqueue a REMOVE * LINK_ACTION event. */ /* ARGSUSED */ static int nwamd_destroy_ncu(nwam_ncu_handle_t ncuh, void *data) { char *name; uint32_t flags; nwamd_event_t link_event; if (nwam_ncu_get_name(ncuh, &name) != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_destroy_ncu: could not get NCU name"); return (0); } /* Interfaces that exist return DLADM_OPT_ACTIVE flag */ if ((dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL) == DLADM_STATUS_OK && (flags & DLADM_OPT_ACTIVE)) && !nwamd_link_belongs_to_an_aggr(name)) { free(name); return (0); } nlog(LOG_DEBUG, "nwamd_destroy_ncu: destroying ncus for %s", name); link_event = nwamd_event_init_link_action(name, NWAM_ACTION_REMOVE); if (link_event != NULL) nwamd_event_enqueue(link_event); free(name); return (0); } /* * Called when nwamd is starting up. * * Walk all NCUs and destroy any NCU from the Automatic NCP without an * underlying interface (assumption here is that the interface was removed * when nwam was disabled). * * Walk the physical interfaces and create ADD LINK_ACTION event, which * will create appropriate interface and link NCUs in the Automatic NCP. */ void nwamd_walk_physical_configuration(void) { nwam_ncp_handle_t ncph; datalink_class_t dlclass = DATALINK_CLASS_PHYS; zoneid_t zoneid = getzoneid(); (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) == 0 && active_ncph != NULL) { ncph = active_ncph; } else { if (nwam_ncp_read(NWAM_NCP_NAME_AUTOMATIC, 0, &ncph) != NWAM_SUCCESS) { ncph = NULL; } } /* destroy NCUs for interfaces that don't exist */ if (ncph != NULL) { (void) nwam_ncp_walk_ncus(ncph, nwamd_destroy_ncu, NULL, NWAM_FLAG_NCU_TYPE_LINK, NULL); } /* In non-global zones NWAM can support VNICs */ if (zoneid != GLOBAL_ZONEID) dlclass |= DATALINK_CLASS_VNIC; /* create NCUs for interfaces without NCUs */ (void) dladm_walk(ncu_create_link_action_event, dld_handle, ncph, dlclass, DATALINK_ANY_MEDIATYPE, DLADM_OPT_ACTIVE); if (strcmp(active_ncp, NWAM_NCP_NAME_AUTOMATIC) != 0 || active_ncph == NULL) { nwam_ncp_free(ncph); } (void) pthread_mutex_unlock(&active_ncp_mutex); } /* * Handle NCU initialization/refresh event. */ void nwamd_ncu_handle_init_event(nwamd_event_t event) { nwamd_object_t object = NULL; nwam_ncu_handle_t ncuh; nwamd_ncu_t *ncu = NULL; nwam_error_t err; nwam_ncu_type_t type; char *name; uint32_t flags; boolean_t new = B_TRUE; nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event(%s)", event->event_object); /* Get base linkname rather than interface:linkname or link:linkname */ err = nwam_ncu_typed_name_to_name(event->event_object, &type, &name); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_handle_init_event: " "nwam_ncu_typed_name_to_name returned %s", nwam_strerror(err)); nwamd_event_do_not_send(event); return; } (void) pthread_mutex_lock(&active_ncp_mutex); if (active_ncph == NULL) { nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: active NCP handle NULL"); nwamd_event_do_not_send(event); free(name); (void) pthread_mutex_unlock(&active_ncp_mutex); return; } err = nwam_ncu_read(active_ncph, event->event_object, type, 0, &ncuh); (void) pthread_mutex_unlock(&active_ncp_mutex); if (err != NWAM_SUCCESS) { nlog(LOG_ERR, "nwamd_ncu_handle_init_event: " "could not read object '%s': %s", event->event_object, nwam_strerror(err)); free(name); nwamd_event_do_not_send(event); return; } if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object)) != NULL) new = B_FALSE; /* * For new NCUs, or interface NCUs, we (re)initialize data from scratch. * For link NCUs, we want to retain object data. */ switch (type) { case NWAM_NCU_TYPE_LINK: if (new) { ncu = nwamd_ncu_init(type, name); } else { ncu = object->nwamd_object_data; nwam_ncu_free(object->nwamd_object_handle); } populate_common_ncu_properties(ncuh, ncu); populate_link_ncu_properties(ncuh, ncu); break; case NWAM_NCU_TYPE_INTERFACE: if (!new) { nwam_ncu_free(object->nwamd_object_handle); nwamd_ncu_free(object->nwamd_object_data); } ncu = nwamd_ncu_init(type, name); populate_common_ncu_properties(ncuh, ncu); populate_ip_ncu_properties(ncuh, ncu); break; default: nlog(LOG_ERR, "unknown ncu type %d", type); free(name); nwam_ncu_free(ncuh); nwamd_event_do_not_send(event); nwamd_object_release(object); return; } if (new) { nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: didn't find " "ncu so create it %s", name); object = nwamd_object_init(NWAM_OBJECT_TYPE_NCU, event->event_object, ncuh, ncu); } else { nlog(LOG_DEBUG, "nwamd_ncu_handle_init_event: refreshing " "ncu %s", name); object->nwamd_object_data = ncu; object->nwamd_object_handle = ncuh; } /* * If the physical link for this NCU doesn't exist in the system, * the state should be UNINITIALIZED/NOT_FOUND. Interfaces that * exist return DLADM_OPT_ACTIVE flag. */ if (dladm_name2info(dld_handle, name, NULL, &flags, NULL, NULL) != DLADM_STATUS_OK || !(flags & DLADM_OPT_ACTIVE)) { nlog(LOG_DEBUG, "nwam_ncu_handle_init_event: " "interface for NCU %s doesn't exist", event->event_object); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_UNINITIALIZED, NWAM_AUX_STATE_NOT_FOUND); free(name); nwamd_object_release(object); return; } /* * If NCU is being initialized (rather than refreshed), the * object_state is INITIALIZED (from nwamd_object_init()). */ if (object->nwamd_object_state == NWAM_STATE_INITIALIZED) { /* * If the NCU is disabled, initial state should be DISABLED. * * Otherwise, the initial state will be * OFFLINE/CONDITIONS_NOT_MET, and the link selection * algorithm will do the rest. */ if (!ncu->ncu_enabled) { object->nwamd_object_state = NWAM_STATE_DISABLED; object->nwamd_object_aux_state = NWAM_AUX_STATE_MANUAL_DISABLE; } else { object->nwamd_object_state = NWAM_STATE_OFFLINE; object->nwamd_object_aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; } } else { nwamd_link_t *link = &ncu->ncu_link; /* * Refresh NCU. Deal with disabled cases first, moving NCUs * that are not disabled - but have the enabled value set - to * the disabled state. Then handle cases where the NCU was * disabled but is no longer. Finally, deal with refresh of * link and interface NCUs, as these are handled differently. */ if (!ncu->ncu_enabled) { if (object->nwamd_object_state != NWAM_STATE_DISABLED) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_MANUAL_DISABLE); } goto done; } else { if (object->nwamd_object_state == NWAM_STATE_DISABLED) { int64_t c; /* * Try to activate the NCU if manual or * prioritized (when priority <= current). */ (void) pthread_mutex_lock(&active_ncp_mutex); c = current_ncu_priority_group; (void) pthread_mutex_unlock(&active_ncp_mutex); if (link->nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_MANUAL || (link->nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED && link->nwamd_link_priority_mode <= c)) { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } else { nwamd_object_set_state (NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); } goto done; } } switch (type) { case NWAM_NCU_TYPE_LINK: if (ncu->ncu_link.nwamd_link_media == DL_WIFI) { /* * Do rescan. If the current state and the * active priority-group do not allow wireless * network selection, then it won't happen. */ (void) nwamd_wlan_scan(ncu->ncu_name); } break; case NWAM_NCU_TYPE_INTERFACE: /* * If interface NCU is offline*, online or in * maintenance, mark it down (from there, it will be * reinitialized to reapply addresses). */ if (object->nwamd_object_state != NWAM_STATE_OFFLINE) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); } else { object->nwamd_object_state = NWAM_STATE_OFFLINE; object->nwamd_object_aux_state = NWAM_AUX_STATE_CONDITIONS_NOT_MET; } break; } } done: if (type == NWAM_NCU_TYPE_LINK && !nwamd_event_enqueued(NWAM_EVENT_TYPE_NCU_CHECK, NWAM_OBJECT_TYPE_NCP, NULL)) { nwamd_create_ncu_check_event(NEXT_FEW_SECONDS); } free(name); nwamd_object_release(object); } void nwamd_ncu_handle_fini_event(nwamd_event_t event) { nwamd_object_t object; nwamd_event_t state_event; nlog(LOG_DEBUG, "nwamd_ncu_handle_fini_event(%s)", event->event_object); /* * Simulate a state event so that the state machine can correctly * disable the NCU. Then free up allocated objects. */ state_event = nwamd_event_init_object_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_UNINITIALIZED); if (state_event == NULL) { nwamd_event_do_not_send(event); return; } nwamd_ncu_handle_state_event(state_event); nwamd_event_fini(state_event); if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object)) == NULL) { nlog(LOG_INFO, "nwamd_ncu_handle_fini_event: " "ncu %s not found", event->event_object); nwamd_event_do_not_send(event); return; } nwamd_object_release_and_destroy(object); } void nwamd_ncu_handle_action_event(nwamd_event_t event) { nwamd_object_t object; (void) pthread_mutex_lock(&active_ncp_mutex); if (strcmp(event->event_msg->nwe_data.nwe_object_action.nwe_parent, active_ncp) != 0) { nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: action for " "inactive NCP %s, nothing to do", event->event_msg->nwe_data.nwe_object_action.nwe_parent); (void) pthread_mutex_unlock(&active_ncp_mutex); return; } (void) pthread_mutex_unlock(&active_ncp_mutex); switch (event->event_msg->nwe_data.nwe_object_action.nwe_action) { case NWAM_ACTION_ENABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_action_event: " "could not find ncu %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_ONLINE) { nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: " "ncu %s already online, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_INITIALIZED); break; case NWAM_ACTION_DISABLE: object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (object == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_action_event: " "could not find ncu %s", event->event_object); nwamd_event_do_not_send(event); return; } if (object->nwamd_object_state == NWAM_STATE_DISABLED) { nlog(LOG_DEBUG, "nwamd_ncu_handle_action_event: " "ncu %s already disabled, nothing to do", event->event_object); nwamd_object_release(object); return; } nwamd_object_release(object); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_MANUAL_DISABLE); break; case NWAM_ACTION_ADD: case NWAM_ACTION_REFRESH: nwamd_ncu_handle_init_event(event); break; case NWAM_ACTION_DESTROY: nwamd_ncu_handle_fini_event(event); break; default: nlog(LOG_INFO, "nwam_ncu_handle_action_event: " "unexpected action"); nwamd_event_do_not_send(event); break; } } void nwamd_ncu_handle_state_event(nwamd_event_t event) { nwamd_object_t object; nwam_state_t old_state, new_state; nwam_aux_state_t new_aux_state; nwamd_ncu_t *ncu; boolean_t is_link, enabled, prioritized = B_FALSE; char linkname[NWAM_MAX_NAME_LEN]; nwam_event_t m = event->event_msg; if ((object = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object)) == NULL) { nlog(LOG_INFO, "nwamd_ncu_handle_state_event %lld: " "state event for nonexistent NCU %s", event->event_id, event->event_object); nwamd_event_do_not_send(event); return; } ncu = object->nwamd_object_data; old_state = object->nwamd_object_state; new_state = event->event_msg->nwe_data.nwe_object_state.nwe_state; new_aux_state = event->event_msg->nwe_data.nwe_object_state.nwe_aux_state; /* * For NCU state changes, we need to supply the parent NCP name also, * regardless of whether the event is handled or not. It is best to * fill this in here as we have the object lock - when we create * object state events we sometimes do not have the object lock, but * at this point in consuming the events (and prior to the associated * event message being sent out) we do. */ (void) strlcpy(m->nwe_data.nwe_object_state.nwe_parent, ncu->ncu_parent, sizeof (m->nwe_data.nwe_object_state.nwe_parent)); /* * If we receive a state change event moving this NCU to * DHCP_TIMED_OUT or UP state but this NCU is already ONLINE, then * ignore this state change event. */ if ((new_aux_state == NWAM_AUX_STATE_IF_DHCP_TIMED_OUT || new_aux_state == NWAM_AUX_STATE_UP) && object->nwamd_object_state == NWAM_STATE_ONLINE) { nlog(LOG_INFO, "nwamd_ncu_handle_state_event: " "NCU %s already online, not going to '%s' state", object->nwamd_object_name, nwam_aux_state_to_string(new_aux_state)); nwamd_event_do_not_send(event); nwamd_object_release(object); return; } if (new_state == object->nwamd_object_state && new_aux_state == object->nwamd_object_aux_state) { nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: " "NCU %s already in state (%s, %s)", object->nwamd_object_name, nwam_state_to_string(new_state), nwam_aux_state_to_string(new_aux_state)); nwamd_object_release(object); return; } if (old_state == NWAM_STATE_MAINTENANCE && (new_state == NWAM_STATE_ONLINE || (new_state == NWAM_STATE_OFFLINE_TO_ONLINE && new_aux_state != NWAM_AUX_STATE_INITIALIZED))) { nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: " "NCU %s cannot transition from state %s to state (%s, %s)", object->nwamd_object_name, nwam_state_to_string(old_state), nwam_state_to_string(new_state), nwam_aux_state_to_string(new_aux_state)); nwamd_event_do_not_send(event); nwamd_object_release(object); return; } object->nwamd_object_state = new_state; object->nwamd_object_aux_state = new_aux_state; nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: changing state for NCU " "%s to (%s, %s)", object->nwamd_object_name, nwam_state_to_string(object->nwamd_object_state), nwam_aux_state_to_string(object->nwamd_object_aux_state)); is_link = (ncu->ncu_type == NWAM_NCU_TYPE_LINK); if (is_link) (void) strlcpy(linkname, ncu->ncu_name, sizeof (linkname)); prioritized = (ncu->ncu_type == NWAM_NCU_TYPE_LINK && ncu->ncu_link.nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED); enabled = ncu->ncu_enabled; nwamd_object_release(object); /* * State machine for NCUs */ switch (new_state) { case NWAM_STATE_OFFLINE_TO_ONLINE: if (enabled) { nwamd_ncu_state_machine(event->event_object); } else { nlog(LOG_DEBUG, "nwamd_ncu_handle_state_event: " "cannot move disabled NCU %s online", event->event_object); nwamd_event_do_not_send(event); } break; case NWAM_STATE_ONLINE_TO_OFFLINE: nwamd_ncu_state_machine(event->event_object); break; case NWAM_STATE_ONLINE: /* * We usually don't need to do anything when we're in the * ONLINE state. However, for WiFi we can be in INIT or * SCAN aux states while being ONLINE. */ nwamd_ncu_state_machine(event->event_object); break; case NWAM_STATE_OFFLINE: /* Reassess priority group now member is offline */ if (prioritized) { nwamd_create_ncu_check_event(0); } break; case NWAM_STATE_DISABLED: case NWAM_STATE_UNINITIALIZED: case NWAM_STATE_MAINTENANCE: case NWAM_STATE_DEGRADED: default: /* do nothing */ break; } if (is_link) { if ((new_state == NWAM_STATE_ONLINE_TO_OFFLINE && new_aux_state != NWAM_AUX_STATE_UNINITIALIZED && new_aux_state != NWAM_AUX_STATE_NOT_FOUND) || new_state == NWAM_STATE_DISABLED) { /* * Going offline, propogate down event to IP NCU. Do * not propogate event if new aux state is uninitialized * or not found as these auxiliary states signify * that an NCP switch/device removal is in progress. */ nwamd_propogate_link_up_down_to_ip(linkname, B_FALSE); } if (new_state == NWAM_STATE_ONLINE) { /* gone online, propogate up event to IP NCU */ nwamd_propogate_link_up_down_to_ip(linkname, B_TRUE); } } else { /* If IP NCU is online, reasses priority group */ if (new_state == NWAM_STATE_ONLINE) nwamd_create_ncu_check_event(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) 2010, Oracle and/or its affiliates. All rights reserved. * Copyright (c) 2016, Chris Fraire . */ #ifndef _NCU_H #define _NCU_H #include #include #include #include #include #include #include #include #include #include #include #include #include "events.h" extern pthread_mutex_t active_ncp_mutex; extern pthread_mutex_t active_loc_mutex; extern char active_loc[]; extern uint64_t wireless_scan_interval; extern dladm_wlan_strength_t wireless_scan_level; extern boolean_t wireless_autoconf; extern boolean_t wireless_strict_bssid; /* * NCPs are collections of NCUs. At the moment there is one NCP in the system * and its expected there will never be many. There is a lock on the NCP which * must be obtained to add or remove anything from the NCP. * * NCUs are also kept in a uu list for easy walking. Each NCU has a lock which * is used to protect manipulation of its contents. One of its members is a * reference count which is initialized to 1 when its placed on the NCP. As * references are passed around that should be manipulated as necessary * (helper functions YYY provided). It is removed from the NCP by * ncu_destroy() but the memory containing it is not returned to the free pool * until the reference count falls to 0. * * As we add * more complex system objects their relationship becomes more complex. That * is represented by the links within the NCUs. Reference counts should be * used to maintain the consistency of these links. Care should be used when * walking more complex structures that might contain cycles. */ /* Stores details of last/current WiFi scans */ typedef struct nwamd_wifi_scan { char nwamd_wifi_scan_link[NWAM_MAX_NAME_LEN]; nwam_wlan_t nwamd_wifi_scan_last[NWAMD_MAX_NUM_WLANS]; uint_t nwamd_wifi_scan_last_num; nwam_wlan_t nwamd_wifi_scan_curr[NWAMD_MAX_NUM_WLANS]; uint_t nwamd_wifi_scan_curr_num; boolean_t nwamd_wifi_scan_changed; uint32_t nwamd_wifi_scan_last_time; } nwamd_wifi_scan_t; typedef struct nwamd_link { pthread_mutex_t nwamd_link_wifi_mutex; pthread_t nwamd_link_wifi_scan_thread; pthread_t nwamd_link_wifi_monitor_thread; char nwamd_link_wifi_essid[DLADM_STRSIZE]; char nwamd_link_wifi_bssid[DLADM_STRSIZE]; char nwamd_link_wifi_keyname[DLADM_STRSIZE]; char nwamd_link_wifi_signal_strength[DLADM_STRSIZE]; boolean_t nwamd_link_wifi_add_to_known_wlans; boolean_t nwamd_link_wifi_connected; uint32_t nwamd_link_wifi_security_mode; dladm_wlan_key_t *nwamd_link_wifi_key; nwamd_wifi_scan_t nwamd_link_wifi_scan; uint64_t nwamd_link_wifi_priority; boolean_t nwamd_link_wifi_autoconf; uint32_t nwamd_link_id; uint32_t nwamd_link_media; uint64_t nwamd_link_flags; dlpi_handle_t nwamd_link_dhp; pthread_t nwamd_link_dlpi_thread; uint64_t nwamd_link_activation_mode; uint64_t nwamd_link_priority_mode; uint64_t nwamd_link_priority_group; char *nwamd_link_mac_addr; size_t nwamd_link_mac_addr_len; uint64_t nwamd_link_mtu; char **nwamd_link_autopush; uint_t nwamd_link_num_autopush; } nwamd_link_t; struct nwamd_if_address { sa_family_t family; ipadm_addr_type_t ipaddr_atype; ipadm_addrobj_t ipaddr; boolean_t configured; struct sockaddr_storage conf_addr; /* address configured for */ struct sockaddr_storage conf_stateless_addr; /* this nwamd_if_address */ struct nwamd_if_address *next; }; typedef struct nwamd_if { boolean_t nwamd_if_dhcp_requested; boolean_t nwamd_if_dhcp_configured; boolean_t nwamd_if_stateful_requested; boolean_t nwamd_if_stateful_configured; boolean_t nwamd_if_stateless_requested; boolean_t nwamd_if_stateless_configured; struct nwamd_if_address *nwamd_if_list; struct sockaddr_in nwamd_if_ipv4_default_route; boolean_t nwamd_if_ipv4_default_route_set; struct sockaddr_in6 nwamd_if_ipv6_default_route; boolean_t nwamd_if_ipv6_default_route_set; boolean_t nwamd_if_ipv4; boolean_t nwamd_if_ipv6; } nwamd_if_t; typedef struct nwamd_ncu { nwam_ncu_type_t ncu_type; char *ncu_name; char ncu_parent[NWAM_MAX_NAME_LEN]; boolean_t ncu_enabled; /* whether NCU has been enabled or not */ union { nwamd_link_t u_link; nwamd_if_t u_if; } ncu_node; } nwamd_ncu_t; #define ncu_link ncu_node.u_link #define ncu_if ncu_node.u_if #define LOOPBACK_IF "lo0" struct nwamd_dhcp_thread_arg { char *name; dhcp_ipc_type_t type; ipadm_addrobj_t ipaddr; volatile uint32_t *guard; }; #define WIRELESS_SCAN_INTERVAL_DEFAULT 120 #define WIRELESS_SCAN_INTERVAL_MIN 30 #define WIRELESS_SCAN_REQUESTED_INTERVAL_MIN 10 #define WIRELESS_MONITOR_SIGNAL_INTERVAL 10 #define WIRELESS_RETRY_INTERVAL 30 #define WIRELESS_SCAN_LEVEL_DEFAULT DLADM_WLAN_STRENGTH_WEAK #define NWAMD_DHCP_RETRIES 5 #define NWAMD_DHCP_RETRY_WAIT_TIME 10 #define NWAMD_READONLY_RETRY_INTERVAL 5 /* * This dladm and ipadm handles are opened before interfaces are initialized * and closed only when nwamd shuts down. */ extern dladm_handle_t dld_handle; extern ipadm_handle_t ipadm_handle; extern nwamd_object_t nwamd_ncu_object_find(nwam_ncu_type_t, const char *); extern void nwamd_log_ncus(void); extern void nwamd_ncu_free(nwamd_ncu_t *); /* WLAN functions */ extern void nwamd_set_selected_connected(nwamd_ncu_t *, boolean_t, boolean_t); extern nwam_error_t nwamd_wlan_select(const char *, const char *, const char *, uint32_t, boolean_t); extern nwam_error_t nwamd_wlan_set_key(const char *, const char *, const char *, uint32_t, uint_t, char *); extern nwam_error_t nwamd_wlan_scan(const char *); extern void nwamd_wlan_connect(const char *); extern boolean_t nwamd_wlan_connected(nwamd_object_t); extern void nwamd_wlan_monitor_signal(const char *); extern void nwamd_ncu_create_periodic_scan_event(nwamd_object_t); extern dladm_wlan_key_t *nwamd_wlan_get_key_named(const char *, uint32_t); extern void nwamd_set_key_name(const char *, const char *, char *, size_t); /* Link functions */ extern link_state_t nwamd_get_link_state(const char *); extern const char *nwamd_sockaddr_to_str(const struct sockaddr *, char *, size_t); extern void nwamd_propogate_link_up_down_to_ip(const char *, boolean_t); extern void nwamd_set_unset_link_properties(nwamd_ncu_t *, boolean_t); /* DLPI event hooking */ extern void nwamd_dlpi_add_link(nwamd_object_t); extern void nwamd_dlpi_delete_link(nwamd_object_t); /* IP functions */ extern boolean_t nwamd_static_addresses_configured(nwamd_ncu_t *, sa_family_t); extern void nwamd_plumb_interface(nwamd_ncu_t *, sa_family_t); extern void nwamd_unplumb_interface(nwamd_ncu_t *, sa_family_t); extern boolean_t nwamd_dhcp_managing(int, nwamd_ncu_t *); extern void nwamd_configure_interface_addresses(nwamd_ncu_t *); extern char *nwamd_get_dhcpinfo_data(const char *, char *); extern void nwamd_dhcp_release(const char *); extern void nwamd_add_default_routes(nwamd_ncu_t *); extern void nwamd_add_route(struct sockaddr *, struct sockaddr *, struct sockaddr *, const char *); /* NCU value set/get functions */ extern nwam_error_t nwamd_set_ncu_uint(nwam_ncu_handle_t, uint64_t *, uint_t, const char *); extern nwam_error_t nwamd_set_ncu_string(nwam_ncu_handle_t, char **, uint_t, const char *); extern nwam_error_t nwamd_get_ncu_uint(nwam_ncu_handle_t, nwam_value_t *, uint64_t **, uint_t *, const char *); extern nwam_error_t nwamd_get_ncu_string(nwam_ncu_handle_t, nwam_value_t *, char ***, uint_t *, const char *); extern nwam_error_t nwamd_get_ncu_boolean(nwam_ncu_handle_t, nwam_value_t *, boolean_t **, uint_t *, const char *); extern void nwamd_walk_physical_configuration(void); #endif /* _NCU_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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "conditions.h" #include "events.h" #include "ncp.h" #include "ncu.h" #include "objects.h" #include "util.h" /* * ncu_ip.c - contains routines that are IP interface-specific for NCUs. */ #define STATELESS_RUNNING (IFF_RUNNING | IFF_UP | IFF_ADDRCONF) #define DHCP_RUNNING (IFF_RUNNING | IFF_UP | IFF_DHCPRUNNING) static void nwamd_dhcp(const char *, ipadm_addrobj_t, dhcp_ipc_type_t); static void nwamd_down_interface(const char *, ipadm_addr_type_t, const char *); static boolean_t stateless_running(const nwamd_ncu_t *); /* * Given a sockaddr representation of an IPv4 or IPv6 address returns the * string representation. Note that 'sockaddr' should point at the correct * sockaddr structure for the address family (sockaddr_in for AF_INET or * sockaddr_in6 for AF_INET6) or alternatively at a sockaddr_storage * structure. */ static const char * nwamd_sockaddr2str(const struct sockaddr *addr, char *str, size_t len) { struct sockaddr_in *sin; struct sockaddr_in6 *sin6; const char *straddr; if (addr == NULL) return (NULL); if (addr->sa_family == AF_INET) { /* LINTED E_BAD_PTR_CAST_ALIGN */ sin = (struct sockaddr_in *)addr; straddr = inet_ntop(AF_INET, (void *)&sin->sin_addr, str, len); } else if (addr->sa_family == AF_INET6) { /* LINTED E_BAD_PTR_CAST_ALIGN */ sin6 = (struct sockaddr_in6 *)addr; straddr = inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, str, len); } else { errno = EINVAL; return (NULL); } return (straddr != NULL ? str : NULL); } void nwamd_propogate_link_up_down_to_ip(const char *linkname, boolean_t up) { nwamd_object_t ip_ncu = nwamd_ncu_object_find(NWAM_NCU_TYPE_INTERFACE, linkname); nwamd_ncu_t *ncu; if (ip_ncu == NULL) { nlog(LOG_DEBUG, "nwamd_propogate_link_up_down_to_ip: no IP NCU " "for link %s, cannot propogate %s event", linkname, up ? "up" : "down"); return; } ncu = ip_ncu->nwamd_object_data; if (ncu->ncu_enabled) { if (ip_ncu->nwamd_object_aux_state == NWAM_AUX_STATE_UNINITIALIZED) { nlog(LOG_DEBUG, "nwamd_propogate_link_up_down_to_ip: will not " "propogate link %s event as IP NCU %s is being " "removed", up ? "up" : "down", linkname); } else { nlog(LOG_DEBUG, "nwamd_propogate_link_up_down_to_ip: propogating " "link %s event to interface %s", up ? "up" : "down", linkname); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ip_ncu->nwamd_object_name, up ? NWAM_STATE_OFFLINE_TO_ONLINE : NWAM_STATE_ONLINE_TO_OFFLINE, up ? NWAM_AUX_STATE_INITIALIZED : NWAM_AUX_STATE_CONDITIONS_NOT_MET); } } else { nlog(LOG_DEBUG, "nwamd_propogate_link_up_down_to_ip: not propogating " "link %s event to interface %s, IP NCU is disabled", up ? "up" : "down", linkname); } nwamd_object_release(ip_ncu); } /* * Returns the value associated with the given symbol for the given * interface. The interface may be NULL, in which case the primary * interface is used. * This function substitutes the need to call dhcpinfo(1), thus it is * very similar to the implementation of dhcpinfo(1). * When multiple values need to be returned (e.g., nameservers), they * are separated by a space ' '. */ char * nwamd_get_dhcpinfo_data(const char *sym_name, char *ifname) { dhcp_symbol_t *entry; dhcp_optnum_t optnum; dhcp_ipc_request_t *request = NULL; dhcp_ipc_reply_t *reply; DHCP_OPT *opt; size_t opt_len; char *value; /* return value */ int err; char errmsg[LINE_MAX]; /* if interface is not given, change it to empty string */ if (ifname == NULL) ifname = ""; /* find code and category in dhcp_inittab(5) */ entry = inittab_getbyname(ITAB_CAT_SITE | ITAB_CAT_STANDARD | ITAB_CAT_VENDOR | ITAB_CAT_FIELD, ITAB_CONS_INFO, sym_name); if (entry == NULL) { (void) snprintf(errmsg, LINE_MAX, "unknown identifier: %s", sym_name); goto fail; } /* allocate request */ optnum.code = entry->ds_code; optnum.category = entry->ds_category; optnum.size = entry->ds_max * inittab_type_to_size(entry); request = dhcp_ipc_alloc_request(DHCP_GET_TAG, ifname, &optnum, sizeof (dhcp_optnum_t), DHCP_TYPE_OPTNUM); if (request == NULL) { (void) snprintf(errmsg, LINE_MAX, "failed dhcp alloc request"); goto fail; } /* make the request */ err = dhcp_ipc_make_request(request, &reply, DHCP_IPC_WAIT_DEFAULT); if (err != 0 || reply->return_code != 0) { (void) snprintf(errmsg, LINE_MAX, "%s", dhcp_ipc_strerror(err == 0 ? reply->return_code : err)); } /* get data from the reply */ opt = dhcp_ipc_get_data(reply, &opt_len, NULL); if (opt_len == 0) { (void) snprintf(errmsg, LINE_MAX, "invalid data"); goto fail; } /* check protocol error */ if (opt_len < 2 || (opt_len -2 != opt->len)) { (void) snprintf(errmsg, LINE_MAX, "data length mismatch"); goto fail; } opt_len -= 2; /* decode the data into ascii */ value = inittab_decode(entry, opt->value, opt_len, B_TRUE); if (value == NULL) { (void) snprintf(errmsg, LINE_MAX, "cannot decode reply"); goto fail; } free(request); free(reply); return (value); fail: nlog(LOG_DEBUG, "get_dhcpinfo_data() failed: %s", errmsg); free(request); free(reply); return (NULL); } void nwamd_add_default_routes(nwamd_ncu_t *ncu) { nwamd_if_t *nif = &ncu->ncu_if; char str[INET6_ADDRSTRLEN]; if (nif->nwamd_if_ipv4 && nif->nwamd_if_ipv4_default_route_set) { struct sockaddr_in v4dest, v4mask; v4dest.sin_addr.s_addr = htonl(INADDR_ANY); v4dest.sin_family = AF_INET; v4mask.sin_addr.s_addr = 0; v4mask.sin_family = AF_INET; nlog(LOG_DEBUG, "nwamd_add_default_routes: adding default " "route %s", nwamd_sockaddr2str((struct sockaddr *) &nif->nwamd_if_ipv4_default_route, str, sizeof (str))); nwamd_add_route((struct sockaddr *)&v4dest, (struct sockaddr *)&v4mask, (struct sockaddr *)&nif->nwamd_if_ipv4_default_route, ncu->ncu_name); } if (nif->nwamd_if_ipv6 && nif->nwamd_if_ipv6_default_route_set) { struct sockaddr_in6 v6dest, v6mask; (void) bzero(&v6dest, sizeof (struct sockaddr_in6)); v6dest.sin6_family = AF_INET6; (void) bzero(&v6mask, sizeof (struct sockaddr_in6)); v6mask.sin6_family = AF_INET6; nlog(LOG_DEBUG, "nwamd_add_default_routes: adding default " "route %s", nwamd_sockaddr2str((struct sockaddr *) &nif->nwamd_if_ipv6_default_route, str, sizeof (str))); nwamd_add_route((struct sockaddr *)&v6dest, (struct sockaddr *)&v6mask, (struct sockaddr *)&nif->nwamd_if_ipv6_default_route, ncu->ncu_name); } } /* * Returns the nwamd_if_address structure for the given static address, * NULL if not found. */ static struct nwamd_if_address * find_static_address(const struct sockaddr_storage *addr, const nwamd_ncu_t *ncu) { struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list; struct sockaddr_storage saddr; char str[INET6_ADDRSTRLEN]; nlog(LOG_DEBUG, "find_static_address: %s", nwamd_sockaddr2str((struct sockaddr *)addr, str, sizeof (str))); for (nifap = nifa; nifap != NULL; nifap = nifap->next) { if (nifap->ipaddr_atype != IPADM_ADDR_STATIC || ipadm_get_addr(nifap->ipaddr, &saddr) != IPADM_SUCCESS) continue; if (sockaddrcmp(addr, &saddr)) return (nifap); } return (NULL); } /* * Returns the nwamd_if_address structure representing the non-static address * in the NCU. For IPv6, both stateless and stateful (DHCPv6) share the same * nwamd_if_address. Will only return the nwamd_if_address if the relevant * address is configured (v4 DHCP, v6 either stateless or stateless) for the * NCU. Returns NULL if the structure is not found. */ static struct nwamd_if_address * find_nonstatic_address(const nwamd_ncu_t *ncu, sa_family_t family) { struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list; const nwamd_if_t *u_if = &ncu->ncu_if; nlog(LOG_DEBUG, "find_nonstatic_address for %s %s", (family == AF_INET ? "IPv4" : "IPv6"), ncu->ncu_name); for (nifap = nifa; nifap != NULL; nifap = nifap->next) { if (nifap->ipaddr_atype == IPADM_ADDR_STATIC) continue; if (family == AF_INET) { if (nifap->ipaddr_atype == IPADM_ADDR_DHCP && u_if->nwamd_if_dhcp_requested) return (nifap); } else if (family == AF_INET6) { if (nifap->ipaddr_atype == IPADM_ADDR_IPV6_ADDRCONF && (u_if->nwamd_if_stateful_requested || u_if->nwamd_if_stateless_requested)) return (nifap); } } return (NULL); } /* * Returns the nwamd_if_address structure that configured the given address, * NULL if not found. */ static struct nwamd_if_address * find_configured_address(const struct sockaddr_storage *addr, const nwamd_ncu_t *ncu) { struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list; char str[INET6_ADDRSTRLEN]; nlog(LOG_DEBUG, "find_configured_address: %s", nwamd_sockaddr2str((struct sockaddr *)addr, str, sizeof (str))); for (nifap = nifa; nifap != NULL; nifap = nifap->next) { if (sockaddrcmp(addr, &nifap->conf_addr) || sockaddrcmp(addr, &nifap->conf_stateless_addr)) return (nifap); } return (NULL); } /* * Are one or more static addresses configured? */ boolean_t nwamd_static_addresses_configured(nwamd_ncu_t *ncu, sa_family_t family) { struct nwamd_if_address *n; for (n = ncu->ncu_if.nwamd_if_list; n != NULL; n = n->next) { if (n->ipaddr_atype != IPADM_ADDR_STATIC) continue; if ((family == AF_UNSPEC || family == n->family) && n->configured) return (B_TRUE); } nlog(LOG_DEBUG, "no static addresses configured for %s", ncu->ncu_name); return (B_FALSE); } /* * Is DHCP probably managing an address on this index. We decide that it is * probably managing an address if there is an interface with IFF_DHCP set * that isn't in our set of static addresses. Note that IFF_DHCP gets set * on static addresses when we do a dhcp inform and if that list has changed * recently then the result of this function could be erronous. */ boolean_t nwamd_dhcp_managing(int protocol, nwamd_ncu_t *ncu) { struct sockaddr_storage addr; uint64_t flags; boolean_t rv = B_FALSE; ipadm_addr_info_t *addrinfo, *a; ipadm_status_t ipstatus; if ((ipstatus = ipadm_addr_info(ipadm_handle, ncu->ncu_name, &addrinfo, 0, 0)) != IPADM_SUCCESS) { nlog(LOG_ERR, "nwamd_dhcp_managing: " "ipadm_addr_info failed for %s: %s", ncu->ncu_name, ipadm_status2str(ipstatus)); return (B_FALSE); } for (a = addrinfo; a != NULL; a = IA_NEXT(a)) { /* * WARNING: This memcpy() assumes knowledge of the * implementation of getifaddrs() and that it always * uses sockaddr_storage as the backing store for * address information, thus making it possible to * copy the entire structure rather than do it on * the size of the sockaddr according to family. * This assumption is made elsewhere in this file. */ (void) memcpy(&addr, a->ia_ifa.ifa_addr, sizeof (addr)); /* is this address an expected static one? */ if (find_static_address(&addr, ncu) != NULL) continue; /* * For IPv4, DHCPRUNNING flag is set when dhcpagent is in * the process of getting an address, but doesn't have one * yet (interface has 0.0.0.0). For IPv6, DHCPRUNNING flag * is set on the link-local address if trying to get a * stateful address. In both cases, consider the interface * as not being managed by DHCP and skip checking of flags. */ if ((protocol == AF_INET && ((struct sockaddr_in *)&addr)->sin_addr.s_addr == INADDR_ANY) || (protocol == AF_INET6 && IN6_IS_ADDR_LINKLOCAL( &((struct sockaddr_in6 *)&addr)->sin6_addr))) { continue; } flags = a->ia_ifa.ifa_flags; if (flags & IFF_DHCPRUNNING) { /* * If we get here we have an address that has the * DHCP flag set and isn't an expected static address. */ rv = B_TRUE; break; } } ipadm_free_addr_info(addrinfo); return (rv); } /* * Return B_TRUE if IPv4 is requested in the given NCU. */ static boolean_t nwamd_v4_requested(nwamd_ncu_t *ncu) { boolean_t anyv4_requested; nwamd_if_t *u_if; anyv4_requested = B_FALSE; u_if = &ncu->ncu_if; if (u_if->nwamd_if_dhcp_requested) { anyv4_requested = B_TRUE; } else { struct nwamd_if_address *n; for (n = u_if->nwamd_if_list; n != NULL; n = n->next) { if (n->family == AF_INET && n->ipaddr_atype == IPADM_ADDR_STATIC) break; } if (n != NULL) anyv4_requested = B_TRUE; } return (anyv4_requested); } /* * Returns B_TRUE if IPv6 is requested in the given NCU. */ static boolean_t nwamd_v6_requested(nwamd_ncu_t *ncu) { boolean_t anyv6_requested; nwamd_if_t *u_if; anyv6_requested = B_FALSE; u_if = &ncu->ncu_if; if (u_if->nwamd_if_stateful_requested || u_if->nwamd_if_stateless_requested) { anyv6_requested = B_TRUE; } else { struct nwamd_if_address *n; for (n = u_if->nwamd_if_list; n != NULL; n = n->next) { if (n->family == AF_INET6 && n->ipaddr_atype == IPADM_ADDR_STATIC) break; } if (n != NULL) anyv6_requested = B_TRUE; } return (anyv6_requested); } /* * Bring up the ncu if we have the right combination of requested configuration * and actual configuration and up is true, or bring down the ncu if no * addresses are configured, and up is false. */ static void interface_ncu_up_down(nwamd_ncu_t *ncu, boolean_t up) { boolean_t ncu_online; char *name; assert(ncu->ncu_type == NWAM_NCU_TYPE_INTERFACE); /* * If V4 with or without V6 is configured then one of its interfaces * needs to be up for the ncu to come online. If only V6 is requested * then one of its interfaces needs to be up for the ncu to come online. */ ncu_online = B_FALSE; if (nwamd_v4_requested(ncu)) { if (nwamd_dhcp_managing(AF_INET, ncu) || nwamd_static_addresses_configured(ncu, AF_INET)) ncu_online = B_TRUE; } else if (nwamd_v6_requested(ncu)) { if ((nwamd_dhcp_managing(AF_INET6, ncu) || stateless_running(ncu) || nwamd_static_addresses_configured(ncu, AF_INET6))) ncu_online = B_TRUE; } if (nwam_ncu_name_to_typed_name(ncu->ncu_name, ncu->ncu_type, &name) != NWAM_SUCCESS) { nlog(LOG_DEBUG, "interface_ncu_up_down: " "nwam_ncu_name_to_typed_name failed"); return; } if (ncu_online && up) { nlog(LOG_DEBUG, "interface_ncu_up_down: " "bringing %s up", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_UP); } else if (!ncu_online && !up) { nlog(LOG_DEBUG, "interface_ncu_up_down: " "bringing %s down", name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); } free(name); } static void interface_ncu_up(nwamd_ncu_t *ncu) { interface_ncu_up_down(ncu, B_TRUE); } static void interface_ncu_down(nwamd_ncu_t *ncu) { interface_ncu_up_down(ncu, B_FALSE); } static boolean_t stateless_running(const nwamd_ncu_t *ncu) { ipadm_addr_info_t *ainfo, *ainfop; ipadm_status_t ipstatus; boolean_t rv = B_FALSE; uint64_t flags; if ((ipstatus = ipadm_addr_info(ipadm_handle, ncu->ncu_name, &ainfo, 0, 0)) != IPADM_SUCCESS) { nlog(LOG_ERR, "stateless_running: " "ipadm_addr_info failed for %s: %s", ncu->ncu_name, ipadm_status2str(ipstatus)); return (B_FALSE); } for (ainfop = ainfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) { if (ainfop->ia_ifa.ifa_addr->sa_family != AF_INET6) continue; flags = ainfop->ia_ifa.ifa_flags; if (flags & STATELESS_RUNNING) { rv = B_TRUE; break; } } ipadm_free_addr_info(ainfo); return (rv); } /* * Returns the addrinfo associated with the given address. There is always * only one addrinfo for each address. */ static boolean_t addrinfo_for_addr(const struct sockaddr_storage *caddr, const char *ifname, ipadm_addr_info_t **ainfo) { ipadm_addr_info_t *addrinfo, *ainfop, *last = NULL; ipadm_status_t ipstatus; ipstatus = ipadm_addr_info(ipadm_handle, ifname, &addrinfo, 0, 0); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_INFO, "addrinfo_for_addr: " "ipadm_addr_info failed for %s: %s", ifname, ipadm_status2str(ipstatus)); return (B_FALSE); } *ainfo = NULL; for (ainfop = addrinfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) { struct sockaddr_storage addr; (void) memcpy(&addr, ainfop->ia_ifa.ifa_addr, sizeof (addr)); /* * If addresses match, rearrange pointers so that addrinfo * does not contain a, and return a. */ if (sockaddrcmp(&addr, caddr)) { if (last != NULL) last->ia_ifa.ifa_next = ainfop->ia_ifa.ifa_next; else addrinfo = IA_NEXT(ainfop); ainfop->ia_ifa.ifa_next = NULL; *ainfo = ainfop; break; } last = ainfop; } ipadm_free_addr_info(addrinfo); return (*ainfo == NULL ? B_FALSE : B_TRUE); } /* * Returns B_TRUE if the addrinfo associated with the given ipaddr using its * aobjname is found. An addrinfo list is created and returned in ainfo. * Stateless and stateful IPv6 addrinfo have the same aobjname, thus the need * to create a list of addrinfo. */ static boolean_t addrinfo_for_ipaddr(ipadm_addrobj_t ipaddr, const char *ifname, ipadm_addr_info_t **ainfo) { char aobjname[IPADM_AOBJSIZ]; ipadm_addr_info_t *addrinfo, *ainfop; ipadm_addr_info_t *last = NULL; ipadm_status_t ipstatus; ipstatus = ipadm_get_aobjname(ipaddr, aobjname, sizeof (aobjname)); if (ipstatus != IPADM_SUCCESS) return (B_FALSE); ipstatus = ipadm_addr_info(ipadm_handle, ifname, &addrinfo, 0, 0); if (ipstatus != IPADM_SUCCESS) { nlog(LOG_INFO, "addrinfo_for_ipaddr: " "ipadm_addr_info failed for %s: %s", ifname, ipadm_status2str(ipstatus)); return (B_FALSE); } *ainfo = NULL; ainfop = addrinfo; while (ainfop != NULL) { /* If aobjnames match, rearrange pointers to create new list */ if (strcmp(ainfop->ia_aobjname, aobjname) == 0) { ipadm_addr_info_t *match = ainfop; ainfop = IA_NEXT(ainfop); /* move iterator */ if (last != NULL) last->ia_ifa.ifa_next = match->ia_ifa.ifa_next; else addrinfo = ainfop; if (*ainfo == NULL) match->ia_ifa.ifa_next = NULL; else match->ia_ifa.ifa_next = &(*ainfo)->ia_ifa; *ainfo = match; } else { last = ainfop; ainfop = IA_NEXT(ainfop); } } ipadm_free_addr_info(addrinfo); return (*ainfo == NULL ? B_FALSE : B_TRUE); } /* * Add the address provided in the nwamd_if_address. If DHCP is required, * start DHCP. If a static address is configured, create the address; then do * a DHCP_INFORM (in a separate thread) to get other networking configuration * parameters. RTM_NEWADDRs - translated into IF_STATE events - will then * finish the job of bringing the NCU online. */ static boolean_t add_ip_address(const char *ifname, const struct nwamd_if_address *nifa, boolean_t *do_inform) { ipadm_status_t ipstatus; ipadm_addr_info_t *addrinfo = NULL; uint64_t flags; if (nifa->ipaddr_atype == IPADM_ADDR_DHCP) { /* * To make getting a DHCP address asynchronous, call * ipadm_create_addr() in a new thread. */ nlog(LOG_DEBUG, "add_ip_address: " "adding IPv4 DHCP address on %s", ifname); nwamd_dhcp(ifname, nifa->ipaddr, DHCP_START); } else { nlog(LOG_DEBUG, "add_ip_address: adding %s address on %s", (nifa->ipaddr_atype == IPADM_ADDR_STATIC ? "STATIC" : "IPv6 ADDRCONF"), ifname); if ((ipstatus = ipadm_create_addr(ipadm_handle, nifa->ipaddr, IPADM_OPT_ACTIVE | IPADM_OPT_UP)) != IPADM_SUCCESS) { nlog(LOG_ERR, "add_ip_address: " "ipadm_create_addr failed on %s: %s", ifname, ipadm_status2str(ipstatus)); return (B_FALSE); } /* * When creating a static address, ipadm_create_addr() returns * SUCCESS even if duplicate address is detected. Retrieve * the addrinfo to get the flags. */ if (nifa->ipaddr_atype == IPADM_ADDR_STATIC) { /* * Since we are configuring a static address, there * will be just *ONE* addrinfo with the aobjname in * nifa->ipaddr. */ if (!addrinfo_for_ipaddr(nifa->ipaddr, ifname, &addrinfo)) { nlog(LOG_ERR, "add_ip_address: " "could not find addrinfo on %s", ifname); return (B_FALSE); } flags = addrinfo->ia_ifa.ifa_flags; ipadm_free_addr_info(addrinfo); if (flags & IFF_DUPLICATE) { char *object_name; nwam_error_t err; nlog(LOG_INFO, "add_ip_address: " "duplicate address detected on %s", ifname); if ((err = nwam_ncu_name_to_typed_name(ifname, NWAM_NCU_TYPE_INTERFACE, &object_name)) == NWAM_SUCCESS) { nwamd_object_set_state( NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_MAINTENANCE, NWAM_AUX_STATE_IF_DUPLICATE_ADDR); free(object_name); } else { nlog(LOG_ERR, "add_ip_address: " "could not create state event " "for %s: %s", ifname, nwam_strerror(err)); } return (B_FALSE); } /* * Do DHCP_INFORM using async ipadm_refresh_addr(). * Only need to do this once per interface, and we * do *not* need to do it if we are also getting a * dhcp lease; so we only send the INFORM if the * passed-in flag says to, and we clear the flag * once we've initiated the INFORM transaction. */ if (*do_inform) { nwamd_dhcp(ifname, nifa->ipaddr, DHCP_INFORM); *do_inform = B_FALSE; } } } return (B_TRUE); } /* * Adds addresses for the given NCU. */ void nwamd_configure_interface_addresses(nwamd_ncu_t *ncu) { struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list; boolean_t do_inform; /* only need an inform if we're not also getting a dhcp lease */ do_inform = !ncu->ncu_if.nwamd_if_dhcp_requested; nlog(LOG_DEBUG, "nwamd_configure_interface_addresses(%s)", ncu->ncu_name); for (nifap = nifa; nifap != NULL; nifap = nifap->next) { if (nifap->configured) continue; nifap->configured = add_ip_address(ncu->ncu_name, nifap, &do_inform); } } /* * This event tells us that an interface address has appeared or disappeared, * or that the interface flags on an interface have changed. */ void nwamd_ncu_handle_if_state_event(nwamd_event_t event) { nwam_event_t evm; nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; nwam_state_t state; nwam_aux_state_t aux_state; ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (ncu_obj == NULL) { nlog(LOG_INFO, "nwamd_ncu_handle_if_state_event: no object %s", event->event_object); nwamd_event_do_not_send(event); return; } ncu = ncu_obj->nwamd_object_data; evm = event->event_msg; state = ncu_obj->nwamd_object_state; aux_state = ncu_obj->nwamd_object_aux_state; nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: " "if %s, state (%s, %s)", event->event_object, nwam_state_to_string(state), nwam_aux_state_to_string(aux_state)); /* Ensure object is in correct state to handle IF state events */ switch (state) { case NWAM_STATE_OFFLINE_TO_ONLINE: if (aux_state != NWAM_AUX_STATE_IF_WAITING_FOR_ADDR && aux_state != NWAM_AUX_STATE_IF_DHCP_TIMED_OUT) { nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: " "if %s is in invalid aux state %s for IF_STATE " "events", event->event_object, nwam_aux_state_to_string(aux_state)); nwamd_event_do_not_send(event); nwamd_object_release(ncu_obj); return; } break; case NWAM_STATE_ONLINE: /* * We can get addresses from DHCP after we've taken the interface down. * We deal with those below. */ case NWAM_STATE_ONLINE_TO_OFFLINE: case NWAM_STATE_OFFLINE: break; default: nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: " "if %s is in invalid state %s for IF_STATE events", event->event_object, nwam_state_to_string(state)); nwamd_event_do_not_send(event); nwamd_object_release(ncu_obj); return; } if (evm->nwe_data.nwe_if_state.nwe_addr_valid) { struct nwam_event_if_state *if_state; char addrstr[INET6_ADDRSTRLEN]; boolean_t static_addr = B_FALSE, addr_added; boolean_t v4dhcp_running, v6dhcp_running, stateless_running; ipadm_addr_info_t *ai = NULL, *addrinfo = NULL; boolean_t stateless_ai_found = B_FALSE; boolean_t stateful_ai_found = B_FALSE; struct nwamd_if_address *nifa = NULL; nwamd_if_t *u_if; struct sockaddr_storage *addr, ai_addr, *aip = NULL; ushort_t family; uint64_t flags = 0; if_state = &evm->nwe_data.nwe_if_state; u_if = &ncu->ncu_if; family = if_state->nwe_addr.ss_family; addr = &if_state->nwe_addr; addr_added = if_state->nwe_addr_added; v4dhcp_running = B_FALSE; v6dhcp_running = B_FALSE; stateless_running = B_FALSE; nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: addr %s %s", nwamd_sockaddr2str((struct sockaddr *)addr, addrstr, sizeof (addrstr)), addr_added ? "added" : "removed"); /* * Need to get flags for this interface. Get the addrinfo for * the address that generated this IF_STATE event. */ if (addr_added) { /* * Address was added. Find the addrinfo for this * address and the nwamd_if_address corresponding to * this address. */ if (!addrinfo_for_addr(addr, ncu->ncu_name, &ai)) { nlog(LOG_ERR, "nwamd_ncu_handle_if_state_event: " "addrinfo doesn't exist for %s", addrstr); nwamd_event_do_not_send(event); goto valid_done; } addrinfo = ai; flags = addrinfo->ia_ifa.ifa_flags; (void) memcpy(&ai_addr, addrinfo->ia_ifa.ifa_addr, sizeof (ai_addr)); aip = &ai_addr; if (addrinfo->ia_atype == IPADM_ADDR_IPV6_ADDRCONF || addrinfo->ia_atype == IPADM_ADDR_DHCP) nifa = find_nonstatic_address(ncu, family); else if (addrinfo->ia_atype == IPADM_ADDR_STATIC) nifa = find_static_address(addr, ncu); /* * If nwamd_if_address is not found, then this address * isn't one that nwamd created. Remove it. */ if (nifa == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_if_state_event: " "address %s not managed by nwam added, " "removing it", addrstr); nwamd_down_interface(addrinfo->ia_aobjname, addrinfo->ia_atype, ncu->ncu_name); nwamd_event_do_not_send(event); goto valid_done; } /* check flags to determine how intf is configured */ stateless_running = (family == AF_INET6) && ((flags & STATELESS_RUNNING) == STATELESS_RUNNING); v4dhcp_running = (family == AF_INET) && ((flags & DHCP_RUNNING) == DHCP_RUNNING); v6dhcp_running = (family == AF_INET6) && ((flags & DHCP_RUNNING) == DHCP_RUNNING); static_addr = (addrinfo->ia_atype == IPADM_ADDR_STATIC); /* copy the configured address into nwamd_if_address */ if (stateless_running) { (void) memcpy(&nifa->conf_stateless_addr, addrinfo->ia_ifa.ifa_addr, sizeof (struct sockaddr_storage)); } else { (void) memcpy(&nifa->conf_addr, addrinfo->ia_ifa.ifa_addr, sizeof (struct sockaddr_storage)); } } else { /* * Address was removed. Find the nwamd_if_address * that configured this address. */ nifa = find_configured_address(addr, ncu); if (nifa == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_if_state_event: " "address %s not managed by nwam removed, " "nothing to do", addrstr); nwamd_event_do_not_send(event); goto valid_done; } if (addrinfo_for_ipaddr(nifa->ipaddr, ncu->ncu_name, &ai)) { ipadm_addr_info_t *a; for (a = ai; a != NULL; a = IA_NEXT(a)) { struct sockaddr_storage stor; (void) memcpy(&stor, a->ia_ifa.ifa_addr, sizeof (stor)); /* * Since multiple addrinfo can have * the same ipaddr, find the one for * the address that generated this * state event. */ if (sockaddrcmp(addr, &stor)) { flags = a->ia_ifa.ifa_flags; (void) memcpy(&ai_addr, a->ia_ifa.ifa_addr, sizeof (ai_addr)); aip = &ai_addr; addrinfo = a; } /* * Stateful and stateless IPv6 * addrinfo have the same aobjname. * Use the flags to determine which * address is present in the system. */ if (family == AF_INET6) { stateless_ai_found = (a->ia_ifa.ifa_flags & STATELESS_RUNNING); stateful_ai_found = (a->ia_ifa.ifa_flags & DHCP_RUNNING); } } } } /* Set the flags in the event for listeners */ evm->nwe_data.nwe_if_state.nwe_flags = flags; if (family == AF_INET && !addr_added) { /* * Check for failure due to CR 6745448: if we get a * report that an address has been deleted, then check * for interface up, datalink down, and actual address * non-zero. If that combination is seen, then this is * a DHCP cached lease, and we need to remove it from * the system, or it'll louse up the kernel routes * (which aren't smart enough to avoid dead * interfaces). */ if (((struct sockaddr_in *)addr)->sin_addr.s_addr == INADDR_ANY && aip != 0) { struct sockaddr_in *a; char astr[INET6_ADDRSTRLEN]; a = (struct sockaddr_in *)aip; if ((flags & IFF_UP) && !(flags & IFF_RUNNING) && a->sin_addr.s_addr != INADDR_ANY) { nlog(LOG_DEBUG, "nwamd_ncu_handle_if_state_event: " "bug workaround: clear out addr " "%s on %s", nwamd_sockaddr2str ((struct sockaddr *)a, astr, sizeof (astr)), ncu->ncu_name); nwamd_down_interface( addrinfo->ia_aobjname, IPADM_ADDR_DHCP, ncu->ncu_name); } goto valid_done; } } /* * If we received an RTM_NEWADDR and the IFF_UP flags has not * been set, ignore this IF_STATE event. Once the IFF_UP flag * is set, we'll get another RTM_NEWADDR message. */ if (addr_added & !(flags & IFF_UP)) { nlog(LOG_INFO, "nwamd_ncu_handle_if_state_event: " "address %s added on %s without IFF_UP flag (%x), " "ignoring IF_STATE event", addrstr, ncu->ncu_name, flags); nwamd_event_do_not_send(event); goto valid_done; } /* * Has the address really been removed? Sometimes spurious * RTM_DELADDRs are generated, so we need to ensure that * the address is really gone. If IFF_DUPLICATE is set, * we're getting the RTM_DELADDR due to DAD, so don't test * in that case. */ if (!addr_added && !(flags & IFF_DUPLICATE)) { if (aip != 0 && sockaddrcmp(addr, aip)) { nlog(LOG_INFO, "nwamd_ncu_handle_if_state_event: " "address %s is not really gone from %s, " "ignoring IF_STATE event", addrstr, ncu->ncu_name); nwamd_event_do_not_send(event); goto valid_done; } } if (addr_added) { /* * Address has been added. * * We need to make sure that we really want to keep * this address. There is a race where we requested an * address but by the time we got here we don't really * want it and need to remove it. * * Once we decide we want the address adjust the ncu * state accordingly. For example if this address is * enough move online. */ if (u_if->nwamd_if_dhcp_requested && v4dhcp_running) { u_if->nwamd_if_dhcp_configured = B_TRUE; } else if (u_if->nwamd_if_stateful_requested && v6dhcp_running) { u_if->nwamd_if_stateful_configured = B_TRUE; } else if (u_if->nwamd_if_stateless_requested && stateless_running) { u_if->nwamd_if_stateless_configured = B_TRUE; } else if (!static_addr) { /* * This is something we didn't expect. Remove * the address. */ nwamd_down_interface(addrinfo->ia_aobjname, addrinfo->ia_atype, ncu->ncu_name); nifa->configured = B_FALSE; goto valid_done; } /* * The address looks valid so mark configured and * move online if we either have a v4 address if * v4 is configured or a v6 address if only v6 is * configured. */ nifa->configured = B_TRUE; if (state != NWAM_STATE_ONLINE) interface_ncu_up(ncu); /* * Refresh network/location since we may also have other * DHCP information. We might have to restore it first * in case it is in maintenance. */ nlog(LOG_DEBUG, "nwamd_handle_if_state_event: " "refreshing %s as we may have other " "DHCP information", NET_LOC_FMRI); (void) smf_restore_instance(NET_LOC_FMRI); if (smf_refresh_instance(NET_LOC_FMRI) != 0) { nlog(LOG_ERR, "nwamd_ncu_handle_if_state_" "event: refresh of %s " "failed", NET_LOC_FMRI); } } else if (state == NWAM_STATE_ONLINE || state == NWAM_STATE_OFFLINE_TO_ONLINE) { /* * Address has been removed. Only pay attention to * disappearing addresses if we are online or coming * online. * * Undo whatever configuration is necessary. Note * that this may or may not cause the NCU to go down. * We can get RTM_DELADDRs for duplicate addresses * so deal with this seperately. */ nifa->configured = B_FALSE; if (!static_addr && family == AF_INET) { u_if->nwamd_if_dhcp_configured = B_FALSE; } else if (!static_addr && family == AF_INET6) { /* * The address is already gone. When looking * for the addrinfo (using aobjname in * ipaddr), we found addrinfo for either one * or both stateless and stateful. Using the * flags we determined whether each was * configured or not. Update the flags here * accordingly. */ u_if->nwamd_if_stateful_configured = stateless_ai_found; u_if->nwamd_if_stateless_configured = stateful_ai_found; } if (flags & IFF_DUPLICATE) { nlog(LOG_INFO, "nwamd_ncu_handle_if_state_event: " "duplicate address detected on %s", ncu->ncu_name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_MAINTENANCE, NWAM_AUX_STATE_IF_DUPLICATE_ADDR); } else { interface_ncu_down(ncu); } } valid_done: ipadm_free_addr_info(ai); } nwamd_object_release(ncu_obj); } void nwamd_ncu_handle_if_action_event(nwamd_event_t event) { nwamd_object_t ncu_obj; nlog(LOG_DEBUG, "if action event %s", event->event_object[0] == '\0' ? "n/a" : event->event_object); ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (ncu_obj == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_if_action_event: no object"); nwamd_event_do_not_send(event); return; } nwamd_object_release(ncu_obj); } /* * Remove the address in the given aobjname. IPADM_OPT_RELEASE is specified * for a DHCP address and specifies that the DHCP lease should also be released. * ifname is only used for nlog(). */ static void nwamd_down_interface(const char *aobjname, ipadm_addr_type_t atype, const char *ifname) { ipadm_status_t ipstatus; uint32_t rflags = (atype == IPADM_ADDR_DHCP ? IPADM_OPT_RELEASE : 0); nlog(LOG_DEBUG, "nwamd_down_interface: %s [aobjname = %s]", ifname, aobjname); if ((ipstatus = ipadm_delete_addr(ipadm_handle, aobjname, IPADM_OPT_ACTIVE | rflags)) != IPADM_SUCCESS) { nlog(LOG_ERR, "nwamd_down_interface: " "ipadm_delete_addr failed on %s: %s", ifname, ipadm_status2str(ipstatus)); } } static void unconfigure_addresses(nwamd_ncu_t *ncu, sa_family_t af) { struct nwamd_if_address *nifap, *nifa = ncu->ncu_if.nwamd_if_list; for (nifap = nifa; nifap != NULL; nifap = nifap->next) if (af == AF_UNSPEC || nifap->family == af) nifap->configured = B_FALSE; } static void dhcp_release(const char *ifname) { ipadm_addr_info_t *ainfo, *ainfop; if (ipadm_addr_info(ipadm_handle, ifname, &ainfo, 0, 0) != IPADM_SUCCESS) return; for (ainfop = ainfo; ainfop != NULL; ainfop = IA_NEXT(ainfop)) { if (ainfop->ia_atype == IPADM_ADDR_DHCP) nwamd_down_interface(ainfop->ia_aobjname, ainfop->ia_atype, ifname); } ipadm_free_addr_info(ainfo); } static void nwamd_plumb_unplumb_interface(nwamd_ncu_t *ncu, sa_family_t af, boolean_t plumb) { char *ifname = ncu->ncu_name; nwamd_if_t *u_if = &ncu->ncu_if; ipadm_status_t ipstatus; nlog(LOG_DEBUG, "nwamd_plumb_unplumb_interface: %s %s %s", (plumb ? "plumb" : "unplumb"), (af == AF_INET ? "IPv4" : "IPv6"), ifname); if (plumb) { ipstatus = ipadm_create_if(ipadm_handle, ifname, af, IPADM_OPT_ACTIVE); } else { /* release DHCP address, if any */ if (af == AF_INET) dhcp_release(ifname); ipstatus = ipadm_delete_if(ipadm_handle, ifname, af, IPADM_OPT_ACTIVE); } if (ipstatus != IPADM_SUCCESS) { if ((plumb && ipstatus != IPADM_IF_EXISTS) || (!plumb && ipstatus != IPADM_ENXIO)) { nlog(LOG_ERR, "nwamd_plumb_unplumb_interface: " "%s %s failed for %s: %s", (plumb ? "plumb" : "unplumb"), (af == AF_INET ? "IPv4" : "IPv6"), ifname, ipadm_status2str(ipstatus)); } } /* Unset flags */ if (!plumb) { unconfigure_addresses(ncu, af); switch (af) { case AF_INET: u_if->nwamd_if_dhcp_configured = B_FALSE; break; case AF_INET6: u_if->nwamd_if_stateful_configured = B_FALSE; u_if->nwamd_if_stateless_configured = B_FALSE; break; } } } void nwamd_plumb_interface(nwamd_ncu_t *ncu, sa_family_t af) { /* * We get all posssible privs by calling nwamd_deescalate(). During * startup opening /dev/dld (data link management) needs all privs * because we don't have access to /etc/security/device_policy yet. */ nwamd_escalate(); nwamd_plumb_unplumb_interface(ncu, af, B_TRUE); nwamd_deescalate(); } void nwamd_unplumb_interface(nwamd_ncu_t *ncu, sa_family_t af) { nwamd_plumb_unplumb_interface(ncu, af, B_FALSE); } static void * start_dhcp_thread(void *arg) { struct nwamd_dhcp_thread_arg *thread_arg = arg; nwamd_object_t ncu_obj; dhcp_ipc_type_t type; char *name; ipadm_addrobj_t ipaddr; ipadm_status_t ipstatus; int retries = 0; name = thread_arg->name; type = thread_arg->type; ipaddr = thread_arg->ipaddr; retry: /* Make sure the NCU is in appropriate state for DHCP command */ ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_INTERFACE, name); if (ncu_obj == NULL) { nlog(LOG_ERR, "start_dhcp: no IP object %s", name); return (NULL); } if (ncu_obj->nwamd_object_state != NWAM_STATE_OFFLINE_TO_ONLINE && ncu_obj->nwamd_object_state != NWAM_STATE_ONLINE) { nlog(LOG_INFO, "start_dhcp: IP NCU %s is in invalid state " "for DHCP command", ncu_obj->nwamd_object_name); nwamd_object_release(ncu_obj); return (NULL); } nwamd_object_release(ncu_obj); switch (type) { case DHCP_INFORM: { char aobjname[IPADM_AOBJSIZ]; if ((ipstatus = ipadm_get_aobjname(ipaddr, aobjname, sizeof (aobjname))) != IPADM_SUCCESS) { nlog(LOG_ERR, "start_dhcp: " "ipadm_get_aobjname failed for %s: %s", name, ipadm_status2str(ipstatus)); goto done; } ipstatus = ipadm_refresh_addr(ipadm_handle, aobjname, IPADM_OPT_ACTIVE | IPADM_OPT_INFORM); break; } case DHCP_START: ipstatus = ipadm_create_addr(ipadm_handle, ipaddr, IPADM_OPT_ACTIVE); break; default: nlog(LOG_ERR, "start_dhcp: invalid dhcp_ipc_type_t: %d", type); goto done; } if (ipstatus == IPADM_DHCP_IPC_TIMEOUT) { /* * DHCP timed out: for DHCP_START requests, change state for * this NCU and euqueue event to check NCU priority-groups; * for DHCP_INFORM requests, nothing to do. */ if (type == DHCP_START) { char *object_name; nlog(LOG_INFO, "start_dhcp: DHCP_START timed out for %s", name); if (nwam_ncu_name_to_typed_name(name, NWAM_NCU_TYPE_INTERFACE, &object_name) != NWAM_SUCCESS) { nlog(LOG_ERR, "start_dhcp: " "nwam_ncu_name_to_typed_name failed for %s", name); goto done; } nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_IF_DHCP_TIMED_OUT); nwamd_create_ncu_check_event(0); free(object_name); } else { nlog(LOG_INFO, "start_dhcp: DHCP_INFORM timed out for %s", name); } } else if ((ipstatus == IPADM_DHCP_IPC_ERROR || ipstatus == IPADM_IPC_ERROR) && retries++ < NWAMD_DHCP_RETRIES) { /* * Retry DHCP request as we may have been unplumbing as part * of the configuration phase. */ nlog(LOG_ERR, "start_dhcp: ipadm_%s_addr on %s returned: %s, " "retrying in %d sec", (type == DHCP_START ? "create" : "refresh"), name, ipadm_status2str(ipstatus), NWAMD_DHCP_RETRY_WAIT_TIME); (void) sleep(NWAMD_DHCP_RETRY_WAIT_TIME); goto retry; } else if (ipstatus != IPADM_SUCCESS) { nlog(LOG_ERR, "start_dhcp: ipadm_%s_addr failed for %s: %s", (type == DHCP_START ? "create" : "refresh"), name, ipadm_status2str(ipstatus)); } done: free(name); free(arg); return (NULL); } static void nwamd_dhcp(const char *ifname, ipadm_addrobj_t ipaddr, dhcp_ipc_type_t cmd) { struct nwamd_dhcp_thread_arg *arg; pthread_attr_t attr; nlog(LOG_DEBUG, "nwamd_dhcp: starting DHCP %s thread for %s", dhcp_ipc_type_to_string(cmd), ifname); arg = malloc(sizeof (*arg)); if (arg == NULL) { nlog(LOG_ERR, "nwamd_dhcp: error allocating memory for " "dhcp request"); return; } arg->name = strdup(ifname); arg->type = cmd; arg->ipaddr = ipaddr; (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create(NULL, &attr, start_dhcp_thread, arg) == -1) { nlog(LOG_ERR, "nwamd_dhcp: cannot start dhcp thread"); free(arg->name); free(arg); (void) pthread_attr_destroy(&attr); return; } (void) pthread_attr_destroy(&attr); } /* * 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) 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 #include #include #include #include #include #include #include #include "events.h" #include "llp.h" #include "objects.h" #include "ncp.h" #include "ncu.h" #include "known_wlans.h" #include "util.h" /* * ncu_phys.c - contains routines that are physical-link specific. * Mostly WiFi code. */ /* * Get link state from kstats. Used to determine initial link state for * cases where drivers do not support DL_NOTE_LINK_UP/DOWN. If link * state is LINK_STATE_UNKNOWN, we assume the link is up and the IP NCU * timeout will cause us to move on to other links. */ link_state_t nwamd_get_link_state(const char *name) { kstat_ctl_t *kcp; kstat_t *ksp; char module[DLPI_LINKNAME_MAX]; uint_t instance; link_state_t link_state = LINK_STATE_UNKNOWN; if ((kcp = kstat_open()) == NULL) return (link_state); if (dlpi_parselink(name, module, &instance) != DLPI_SUCCESS) goto out; if ((ksp = kstat_lookup(kcp, module, instance, "mac")) == NULL) { /* * The kstat query could fail if the underlying MAC * driver was already detached. */ goto out; } if (kstat_read(kcp, ksp, NULL) == -1) goto out; (void) dladm_kstat_value(ksp, "link_state", KSTAT_DATA_UINT32, &link_state); out: (void) kstat_close(kcp); return (link_state); } /* * Set/unset link propeties. At present, these are MAC address, link MTU and * autopush modules. We set MAC address last as setting it may cause a chip * reset which can prevent other device property setting succeeding. */ void nwamd_set_unset_link_properties(nwamd_ncu_t *ncu, boolean_t set) { dlpi_handle_t dh = ncu->ncu_link.nwamd_link_dhp; char *addr = set ? ncu->ncu_link.nwamd_link_mac_addr : NULL; uint64_t mtu = set ? ncu->ncu_link.nwamd_link_mtu : 0; char **autopush = set ? ncu->ncu_link.nwamd_link_autopush : NULL; uint_t num_autopush = set ? ncu->ncu_link.nwamd_link_num_autopush : 0; uchar_t *hwaddr = NULL, curraddr[DLPI_PHYSADDR_MAX]; size_t hwaddrlen = DLPI_PHYSADDR_MAX; int retval; dladm_status_t status; char mtustr[DLADM_PROP_VAL_MAX]; char *cp; char errmsg[DLADM_STRSIZE]; uint_t cnt = 1; /* * Set MTU here - either default value (if mtu == 0 indicating it has * not been set) or specified value. */ if (mtu == 0) { cp = mtustr; status = dladm_get_linkprop(dld_handle, ncu->ncu_link.nwamd_link_id, DLADM_PROP_VAL_DEFAULT, "mtu", &cp, &cnt); if (status != DLADM_STATUS_OK) { nlog(LOG_ERR, "nwamd_set_unset_link_properties: " "dladm_get_linkprop failed: %s", dladm_status2str(status, errmsg)); return; } } else { (void) snprintf(mtustr, DLADM_PROP_VAL_MAX, "%lld", mtu); } cp = mtustr; nlog(LOG_DEBUG, "nwamd_set_unset_link_properties: setting MTU of %s " "for link %s", mtustr, ncu->ncu_name); status = dladm_set_linkprop(dld_handle, ncu->ncu_link.nwamd_link_id, "mtu", &cp, 1, DLADM_OPT_ACTIVE); if (status != DLADM_STATUS_OK) { nlog(LOG_ERR, "nwamd_set_unset_link_properties: " "dladm_set_linkprop failed: %s", dladm_status2str(status, errmsg)); } nlog(LOG_DEBUG, "nwamd_set_unset_link_properties: setting %d " "autopush module for link %s", num_autopush, ncu->ncu_name); status = dladm_set_linkprop(dld_handle, ncu->ncu_link.nwamd_link_id, "autopush", autopush, num_autopush, DLADM_OPT_ACTIVE); if (status != DLADM_STATUS_OK) { nlog(LOG_ERR, "nwamd_set_unset_link_properties: " "dladm_set_linkprop failed for autopush property: %s", dladm_status2str(status, errmsg)); } /* * Set physical address - either factory (if link_mac_addr is NULL * or we are unsetting properties) or specified MAC address string. */ if (addr == NULL) { if ((hwaddr = calloc(1, DLPI_PHYSADDR_MAX)) == NULL) { nlog(LOG_ERR, "nwamd_set_unset_link_properties: malloc() failed"); return; } if ((retval = dlpi_get_physaddr(dh, DL_FACT_PHYS_ADDR, hwaddr, &hwaddrlen)) != DLPI_SUCCESS) { nlog(LOG_ERR, "nwamd_set_unset_link_properties: " "could not get physical address for %s: %s", ncu->ncu_name, dlpi_strerror(retval)); free(hwaddr); return; } } else { int addrlen = hwaddrlen; if ((hwaddr = _link_aton(addr, &addrlen)) == NULL) { if (addrlen == -1) { nlog(LOG_ERR, "nwamd_set_unset_link_properties: " "%s: bad address for %s", addr, ncu->ncu_name); return; } else { nlog(LOG_ERR, "nwamd_set_unset_link_properties:" " malloc() failed"); return; } } hwaddrlen = addrlen; } /* * Only set physical address if desired address differs from current - * this avoids unnecessary chip resets for some drivers. */ retval = dlpi_get_physaddr(dh, DL_CURR_PHYS_ADDR, curraddr, &hwaddrlen); if (retval != DLPI_SUCCESS || bcmp(curraddr, hwaddr, hwaddrlen) != 0) { retval = dlpi_set_physaddr(dh, DL_CURR_PHYS_ADDR, hwaddr, hwaddrlen); if (retval != DLPI_SUCCESS) { nlog(LOG_ERR, "nwamd_set_unset_link_properties:" "failed setting mac address on %s: %s", ncu->ncu_name, dlpi_strerror(retval)); } } free(hwaddr); } #define WLAN_ENC(sec) \ ((sec == DLADM_WLAN_SECMODE_WPA ? "WPA" : \ (sec == DLADM_WLAN_SECMODE_WEP ? "WEP" : "none"))) #define NEED_ENC(sec) \ (sec == DLADM_WLAN_SECMODE_WPA || sec == DLADM_WLAN_SECMODE_WEP) #define WIRELESS_LAN_INIT_COUNT 8 /* * The variable wireless_scan_level specifies the signal level * that we will initiate connections to previously-visited APs * at when we are in the connected state. */ dladm_wlan_strength_t wireless_scan_level = DLADM_WLAN_STRENGTH_WEAK; /* * The variable wireless_scan_interval specifies how often the periodic * scan occurs. */ uint64_t wireless_scan_interval = WIRELESS_SCAN_INTERVAL_DEFAULT; /* * The variable wireless_autoconf specifies if we use dladm_wlan_autoconf() * to connect. */ boolean_t wireless_autoconf = B_FALSE; /* * The variable wireless_strict_bssid specifies if we only connect * to WLANs with BSSIDs that we previously connected to. */ boolean_t wireless_strict_bssid = B_FALSE; /* * We need to ensure scan or connect threads do not run concurrently * on any links - otherwise we get radio interference. Acquire this * lock on entering scan/connect threads to prevent this. */ pthread_mutex_t wireless_mutex = PTHREAD_MUTEX_INITIALIZER; static void scanconnect_entry(void) { (void) pthread_mutex_lock(&wireless_mutex); } static void scanconnect_exit(void) { (void) pthread_mutex_unlock(&wireless_mutex); } /* * Below are functions used to handle storage/retrieval of keys * for a given WLAN. The keys are stored/retrieved using dladm_set_secobj() * and dladm_get_secobj(). */ /* * Convert key hexascii string to raw secobj value. This * code is very similar to convert_secobj() in dladm.c, it would * be good to have a libdladm function to convert values. */ static int key_string_to_secobj_value(char *buf, uint8_t *obj_val, uint_t *obj_lenp, dladm_secobj_class_t class) { size_t buf_len = strlen(buf); nlog(LOG_DEBUG, "before: key_string_to_secobj_value: buf_len = %d", buf_len); if (buf_len == 0) { /* length zero means "delete" */ return (0); } if (buf[buf_len - 1] == '\n') buf[--buf_len] = '\0'; nlog(LOG_DEBUG, "after: key_string_to_secobj_value: buf_len = %d", buf_len); if (class == DLADM_SECOBJ_CLASS_WPA) { /* * Per IEEE802.11i spec, the Pre-shared key (PSK) length should * be between 8 and 63. */ if (buf_len < 8 || buf_len > 63) { nlog(LOG_ERR, "key_string_to_secobj_value:" " invalid WPA key length: buf_len = %d", buf_len); return (-1); } (void) memcpy(obj_val, buf, (uint_t)buf_len); *obj_lenp = buf_len; return (0); } switch (buf_len) { case 5: /* ASCII key sizes */ case 13: (void) memcpy(obj_val, buf, (uint_t)buf_len); *obj_lenp = (uint_t)buf_len; break; case 10: case 26: /* Hex key sizes, not preceded by 0x */ if (hexascii_to_octet(buf, (uint_t)buf_len, obj_val, obj_lenp) != 0) { nlog(LOG_ERR, "key_string_to_secobj_value: invalid WEP key"); return (-1); } break; case 12: case 28: /* Hex key sizes, preceded by 0x */ if (strncmp(buf, "0x", 2) != 0 || hexascii_to_octet(buf + 2, (uint_t)buf_len - 2, obj_val, obj_lenp) != 0) { nlog(LOG_ERR, "key_string_to_secobj_value: invalid WEP key"); return (-1); } break; default: syslog(LOG_ERR, "key_string_to_secobj_value: invalid WEP key length"); return (-1); } return (0); } /* * Callback used on each known WLAN: * return 1 if a secobj, linked with an existing kwown wlan, has the same name * of the secobj that is being created. */ static int find_keyname_cb(nwam_known_wlan_handle_t kwh, void *new_keyname) { nwam_error_t err; nwam_value_t old_key; char **old_keyname; uint_t num_old_keyname, i; if ((err = nwam_known_wlan_get_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_KEYNAME, &old_key)) != NWAM_SUCCESS) { nlog(LOG_ERR, "find_keyname_cb: nwam_known_wlan_get_prop: %s", nwam_strerror(err)); return (0); } if ((err = nwam_value_get_string_array(old_key, &old_keyname, &num_old_keyname)) != NWAM_SUCCESS) { nlog(LOG_ERR, "find_keyname_cb: nwam_value_get_string: %s", nwam_strerror(err)); nwam_value_free(old_key); return (0); } nwam_value_free(old_key); for (i = 0; i < num_old_keyname; i++) { if (strcmp(old_keyname[i], (const char *)new_keyname) == 0) /* Found matching keyname so terminate walk */ return (1); } return (0); } /* * Print the key name format into the appropriate field, then convert any ":" * characters to ".", as ":[1-4]" is the slot indicator, which otherwise * would trip us up. Invalid characters for secobj names are ignored. * The fourth parameter is expected to be of size DLADM_SECOBJ_NAME_MAX. * * (Note that much of the system uses DLADM_WLAN_MAX_KEYNAME_LEN, which is 64 * rather than 32, but that dladm_get_secobj will fail if a length greater than * DLD_SECOBJ_NAME_MAX is seen, and that's 32. This is all horribly broken.) */ void nwamd_set_key_name(const char *essid, const char *bssid, char *name, size_t nsz) { int i, j; char secobj_name[DLADM_WLAN_MAX_KEYNAME_LEN]; /* create a concatenated string with essid and bssid */ if (bssid == NULL || bssid[0] == '\0') { (void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s", essid); } else { (void) snprintf(secobj_name, sizeof (secobj_name), "nwam-%s-%s", essid, bssid); } /* copy only valid chars to the return string, terminating with \0 */ i = 0; /* index into secobj_name */ j = 0; /* index into name */ while (secobj_name[i] != '\0') { if (j == nsz - 1) break; if (secobj_name[i] == ':') { name[j] = '.'; j++; } else if (isalnum(secobj_name[i]) || secobj_name[i] == '.' || secobj_name[i] == '-' || secobj_name[i] == '_') { name[j] = secobj_name[i]; j++; } i++; } name[j] = '\0'; } nwam_error_t nwamd_wlan_set_key(const char *linkname, const char *essid, const char *bssid, uint32_t security_mode, uint_t keyslot, char *raw_key) { nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; nwamd_link_t *link; int ret = 0; uint8_t obj_val[DLADM_SECOBJ_VAL_MAX]; uint_t obj_len = sizeof (obj_val); char obj_name[DLADM_SECOBJ_NAME_MAX]; char obj_tempname[DLADM_SECOBJ_NAME_MAX]; dladm_status_t status; char errmsg[DLADM_STRSIZE]; dladm_secobj_class_t class; if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname)) == NULL) { nlog(LOG_ERR, "nwamd_wlan_set_key: could not find object " "for link %s", linkname); return (NWAM_ENTITY_NOT_FOUND); } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; class = (security_mode == DLADM_WLAN_SECMODE_WEP ? DLADM_SECOBJ_CLASS_WEP : DLADM_SECOBJ_CLASS_WPA); if (key_string_to_secobj_value(raw_key, obj_val, &obj_len, class) != 0) { /* above function logs internally on failure */ nwamd_object_release(ncu_obj); return (NWAM_ERROR_INTERNAL); } nlog(LOG_DEBUG, "nwamd_wlan_set_key: running for link %s", linkname); /* * Name key object for this WLAN so it can be later retrieved. * (bssid is appended if an object, with the same keyname, * already exists and is associated to a known wlan) */ nwamd_set_key_name(essid, NULL, obj_tempname, sizeof (obj_tempname)); (void) nwam_walk_known_wlans(find_keyname_cb, obj_tempname, 0, &ret); /* * We also check if the keyval is the same. The user might want * to use the same key for more APs with the same ESSID. * This can result in a known wlan with multiple BSSIDs */ if (ret == 1) { dladm_wlan_key_t *old_secobj = nwamd_wlan_get_key_named( obj_tempname, security_mode); nlog(LOG_DEBUG, "found existing obj_name %s", obj_tempname); ret = memcmp((*old_secobj).wk_val, obj_val, obj_len); nwamd_set_key_name(essid, ret ? bssid : NULL, obj_name, sizeof (obj_name)); free(old_secobj); } else { nwamd_set_key_name(essid, NULL, obj_name, sizeof (obj_name)); } nlog(LOG_DEBUG, "store_key: obj_name is %s", obj_name); /* * We have validated the new key, so remove the old one. * This will actually delete the keyobj only if the user had set * a wrong key and is replacing it with a new one for the same AP. */ status = dladm_unset_secobj(dld_handle, obj_name, DLADM_OPT_ACTIVE | DLADM_OPT_PERSIST); if (status != DLADM_STATUS_OK && status != DLADM_STATUS_NOTFOUND) { nlog(LOG_ERR, "store_key: could not remove old secure object " "'%s' for key: %s", obj_name, dladm_status2str(status, errmsg)); nwamd_object_release(ncu_obj); return (NWAM_ERROR_INTERNAL); } /* if we're just deleting the key, then we're done */ if (raw_key[0] == '\0') { nwamd_object_release(ncu_obj); return (NWAM_SUCCESS); } status = dladm_set_secobj(dld_handle, obj_name, class, obj_val, obj_len, DLADM_OPT_CREATE | DLADM_OPT_PERSIST | DLADM_OPT_ACTIVE); if (status != DLADM_STATUS_OK) { nlog(LOG_ERR, "store_key: could not create secure object " "'%s' for key: %s", obj_name, dladm_status2str(status, errmsg)); nwamd_object_release(ncu_obj); return (NWAM_ERROR_INTERNAL); } link->nwamd_link_wifi_key = nwamd_wlan_get_key_named(obj_name, security_mode); (void) strlcpy(link->nwamd_link_wifi_keyname, obj_name, sizeof (link->nwamd_link_wifi_keyname)); link->nwamd_link_wifi_security_mode = security_mode; if (security_mode == DLADM_WLAN_SECMODE_WEP) { link->nwamd_link_wifi_key->wk_idx = (keyslot >= 1 && keyslot <= 4) ? keyslot : 1; } /* If link NCU is offline* or online, (re)connect. */ switch (ncu_obj->nwamd_object_state) { case NWAM_STATE_ONLINE: /* if changing the key of the connected WLAN, reconnect */ if (strcmp(essid, link->nwamd_link_wifi_essid) == 0) nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE, NWAM_AUX_STATE_LINK_WIFI_CONNECTING); break; case NWAM_STATE_OFFLINE_TO_ONLINE: /* if we are waiting for the key, connect */ if (ncu_obj->nwamd_object_aux_state == NWAM_AUX_STATE_LINK_WIFI_NEED_KEY) nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_LINK_WIFI_CONNECTING); break; default: break; } nwamd_object_release(ncu_obj); return (NWAM_SUCCESS); } /* * returns NULL if no key was recovered from libdladm. Passing in * security mode of 0 means we don't care what key type it is. */ dladm_wlan_key_t * nwamd_wlan_get_key_named(const char *name, uint32_t security_mode) { dladm_status_t status; char errmsg[DLADM_STRSIZE]; dladm_wlan_key_t *cooked_key; dladm_secobj_class_t class; if (security_mode == DLADM_WLAN_SECMODE_NONE) return (NULL); /* * Newly-allocated key must be freed by caller, or by * subsequent call to nwamd_wlan_get_key_named(). */ if ((cooked_key = malloc(sizeof (dladm_wlan_key_t))) == NULL) { nlog(LOG_ERR, "nwamd_wlan_get_key_named: malloc failed"); return (NULL); } /* * Set name appropriately to retrieve key for this WLAN. Note that we * cannot use the actual wk_name buffer size, as it's two times too * large for dladm_get_secobj. */ (void) strlcpy(cooked_key->wk_name, name, DLADM_SECOBJ_NAME_MAX); nlog(LOG_DEBUG, "nwamd_wlan_get_key_named: len = %d, object = %s\n", strlen(cooked_key->wk_name), cooked_key->wk_name); cooked_key->wk_len = sizeof (cooked_key->wk_val); cooked_key->wk_idx = 1; /* Try the kernel first, then fall back to persistent storage. */ status = dladm_get_secobj(dld_handle, cooked_key->wk_name, &class, cooked_key->wk_val, &cooked_key->wk_len, DLADM_OPT_ACTIVE); if (status != DLADM_STATUS_OK) { nlog(LOG_DEBUG, "nwamd_wlan_get_key_named: " "dladm_get_secobj(TEMP) failed: %s", dladm_status2str(status, errmsg)); status = dladm_get_secobj(dld_handle, cooked_key->wk_name, &class, cooked_key->wk_val, &cooked_key->wk_len, DLADM_OPT_PERSIST); } switch (status) { case DLADM_STATUS_OK: nlog(LOG_DEBUG, "nwamd_wlan_get_key_named: " "dladm_get_secobj succeeded: len %d", cooked_key->wk_len); break; case DLADM_STATUS_NOTFOUND: /* * We do not want an error in the case that the secobj * is not found, since we then prompt for it. */ free(cooked_key); return (NULL); default: nlog(LOG_ERR, "nwamd_wlan_get_key_named: could not get key " "from secure object '%s': %s", cooked_key->wk_name, dladm_status2str(status, errmsg)); free(cooked_key); return (NULL); } if (security_mode != 0) { switch (class) { case DLADM_SECOBJ_CLASS_WEP: if (security_mode == DLADM_WLAN_SECMODE_WEP) return (cooked_key); break; case DLADM_SECOBJ_CLASS_WPA: if (security_mode == DLADM_WLAN_SECMODE_WPA) return (cooked_key); break; default: /* shouldn't happen */ nlog(LOG_ERR, "nwamd_wlan_get_key: invalid class %d", class); break; } /* key type mismatch */ nlog(LOG_ERR, "nwamd_wlan_get_key: key type mismatch" " from secure object '%s'", cooked_key->wk_name); free(cooked_key); return (NULL); } return (cooked_key); } static dladm_wlan_key_t * nwamd_wlan_get_key(const char *essid, const char *bssid, uint32_t security_mode) { char keyname[DLADM_SECOBJ_NAME_MAX]; nwamd_set_key_name(essid, bssid, keyname, DLADM_SECOBJ_NAME_MAX); return (nwamd_wlan_get_key_named(keyname, security_mode)); } /* * Checks if a wireless network can be selected or not. A wireless network * CANNOT be selected if the NCU is DISABLED, or the NCU is OFFLINE or * ONLINE* and has lower priority than the currently active priority-group. * Called with object lock held. */ static boolean_t wireless_selection_possible(nwamd_object_t object) { nwamd_ncu_t *ncu = object->nwamd_object_data; if (ncu->ncu_link.nwamd_link_media != DL_WIFI) return (B_FALSE); (void) pthread_mutex_lock(&active_ncp_mutex); if (object->nwamd_object_state == NWAM_STATE_DISABLED || ((object->nwamd_object_state == NWAM_STATE_OFFLINE || object->nwamd_object_state == NWAM_STATE_ONLINE_TO_OFFLINE) && ncu->ncu_link.nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED && (current_ncu_priority_group == INVALID_PRIORITY_GROUP || ncu->ncu_link.nwamd_link_priority_group > current_ncu_priority_group))) { (void) pthread_mutex_unlock(&active_ncp_mutex); return (B_FALSE); } (void) pthread_mutex_unlock(&active_ncp_mutex); return (B_TRUE); } /* * Update the selected and/or connected values for the * scan data. If these change, we need to trigger a scan * event since the updated values need to be communicated * to the GUI. */ void nwamd_set_selected_connected(nwamd_ncu_t *ncu, boolean_t selected, boolean_t connected) { nwamd_link_t *link = &ncu->ncu_link; nwamd_wifi_scan_t *s = &link->nwamd_link_wifi_scan; int i; boolean_t trigger_scan_event = B_FALSE; for (i = 0; i < s->nwamd_wifi_scan_curr_num; i++) { if (strcmp(s->nwamd_wifi_scan_curr[i].nww_essid, link->nwamd_link_wifi_essid) != 0 || (link->nwamd_link_wifi_bssid[0] != '\0' && strcmp(s->nwamd_wifi_scan_curr[i].nww_bssid, link->nwamd_link_wifi_bssid) != 0)) continue; if (selected) { if (!s->nwamd_wifi_scan_curr[i].nww_selected) trigger_scan_event = B_TRUE; s->nwamd_wifi_scan_curr[i].nww_selected = B_TRUE; } else { if (s->nwamd_wifi_scan_curr[i].nww_selected) trigger_scan_event = B_TRUE; s->nwamd_wifi_scan_curr[i].nww_selected = B_FALSE; } if (connected) { if (!s->nwamd_wifi_scan_curr[i].nww_connected) trigger_scan_event = B_TRUE; s->nwamd_wifi_scan_curr[i].nww_connected = B_TRUE; } else { if (s->nwamd_wifi_scan_curr[i].nww_connected) trigger_scan_event = B_TRUE; s->nwamd_wifi_scan_curr[i].nww_connected = B_FALSE; } } if (trigger_scan_event || s->nwamd_wifi_scan_changed) { nwamd_event_t scan_event = nwamd_event_init_wlan (ncu->ncu_name, NWAM_EVENT_TYPE_WLAN_SCAN_REPORT, connected, s->nwamd_wifi_scan_curr, s->nwamd_wifi_scan_curr_num); if (scan_event != NULL) { /* Avoid sending same scan data multiple times */ s->nwamd_wifi_scan_changed = B_FALSE; nwamd_event_enqueue(scan_event); } } } /* * Callback used on each known WLAN - if the BSSID is matched, set * the ESSID of the hidden WLAN to the known WLAN name. */ static int find_bssid_cb(nwam_known_wlan_handle_t kwh, void *data) { nwamd_link_t *link = data; nwam_error_t err; nwam_value_t bssidval; char **bssids, *name; uint_t num_bssids, i; if ((err = nwam_known_wlan_get_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_BSSIDS, &bssidval)) != NWAM_SUCCESS) { nlog(LOG_ERR, "find_bssid_cb: nwam_known_wlan_get_prop: %s", nwam_strerror(err)); return (0); } if ((err = nwam_value_get_string_array(bssidval, &bssids, &num_bssids)) != NWAM_SUCCESS) { nlog(LOG_ERR, "find_bssid_cb: nwam_value_get_string_array: %s", nwam_strerror(err)); nwam_value_free(bssidval); return (0); } for (i = 0; i < num_bssids; i++) { if (strcmp(bssids[i], link->nwamd_link_wifi_bssid) == 0) { if ((err = nwam_known_wlan_get_name(kwh, &name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "find_bssid_cb: " "nwam_known_wlan_get_name: %s", nwam_strerror(err)); continue; } (void) strlcpy(link->nwamd_link_wifi_essid, name, sizeof (link->nwamd_link_wifi_essid)); free(name); nwam_value_free(bssidval); /* Found ESSID for BSSID so terminate walk */ return (1); } } nwam_value_free(bssidval); return (0); } /* * We may have encountered a BSSID for a hidden WLAN before and as a result * may have a known WLAN entry with this BSSID. Walk known WLANs, searching * for a BSSID match. Called with object lock held. */ static void check_if_hidden_wlan_was_visited(nwamd_link_t *link) { (void) nwam_walk_known_wlans(find_bssid_cb, link, NWAM_FLAG_KNOWN_WLAN_WALK_PRIORITY_ORDER, NULL); } nwam_error_t nwamd_wlan_select(const char *linkname, const char *essid, const char *bssid, uint32_t security_mode, boolean_t add_to_known_wlans) { nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; nwamd_link_t *link; boolean_t found_key = B_FALSE; if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname)) == NULL) { nlog(LOG_ERR, "nwamd_wlan_select: could not find object " "for link %s", linkname); return (NWAM_ENTITY_NOT_FOUND); } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; /* * If wireless selection is not possible because of the current * state or priority-group, then stop. */ if (!wireless_selection_possible(ncu_obj)) { nwamd_object_release(ncu_obj); return (NWAM_ENTITY_INVALID_STATE); } /* unset selected, connected flag for previously connected wlan */ nwamd_set_selected_connected(ncu, B_FALSE, B_FALSE); /* Disconnect to allow new selection to go ahead */ (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); (void) strlcpy(link->nwamd_link_wifi_essid, essid, sizeof (link->nwamd_link_wifi_essid)); (void) strlcpy(link->nwamd_link_wifi_bssid, bssid, sizeof (link->nwamd_link_wifi_bssid)); link->nwamd_link_wifi_security_mode = security_mode; link->nwamd_link_wifi_add_to_known_wlans = add_to_known_wlans; /* If this is a hidden wlan, then essid is empty */ if (link->nwamd_link_wifi_essid[0] == '\0') check_if_hidden_wlan_was_visited(link); /* set selected flag for newly-selected WLAN */ nwamd_set_selected_connected(ncu, B_TRUE, B_FALSE); /* does this WLAN require a key? If so go to NEED_KEY */ if (NEED_ENC(link->nwamd_link_wifi_security_mode)) { /* * nwam secobjs can have two formats: nwam-ESSID-BSSID and * nwam-ESSID. There is no reason for searching through known * wlan keynames since this is only the selection process. */ if ((link->nwamd_link_wifi_key = nwamd_wlan_get_key (link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid, link->nwamd_link_wifi_security_mode)) != NULL) { /* * Found old key format, * known wlans with similar names might exist */ nwamd_set_key_name(link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid, link->nwamd_link_wifi_keyname, DLADM_SECOBJ_NAME_MAX); nlog(LOG_DEBUG, "nwamd_wlan_select: got old format " "WLAN key %s", link->nwamd_link_wifi_keyname); found_key = B_TRUE; } else if ((link->nwamd_link_wifi_key = nwamd_wlan_get_key (link->nwamd_link_wifi_essid, NULL, link->nwamd_link_wifi_security_mode)) != NULL) { nwamd_set_key_name(link->nwamd_link_wifi_essid, NULL, link->nwamd_link_wifi_keyname, DLADM_SECOBJ_NAME_MAX); nlog(LOG_DEBUG, "nwamd_wlan_select: got WLAN key %s", link->nwamd_link_wifi_keyname); found_key = B_TRUE; } else { nlog(LOG_ERR, "nwamd_wlan_select: could not " "find key for WLAN '%s'", link->nwamd_link_wifi_essid); } } else { free(link->nwamd_link_wifi_key); link->nwamd_link_wifi_key = NULL; link->nwamd_link_wifi_keyname[0] = '\0'; } if (NEED_ENC(link->nwamd_link_wifi_security_mode) && !found_key) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_LINK_WIFI_NEED_KEY); } else { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_LINK_WIFI_CONNECTING); } nwamd_object_release(ncu_obj); return (NWAM_SUCCESS); } /* * See if BSSID is in visited list of BSSIDs for known WLAN. Used for * strict BSSID matching (depends on wireless_strict_bssid property value). */ static int bssid_match(nwam_known_wlan_handle_t kwh, void *bssid) { nwam_value_t bssidsval; nwam_error_t err; char **bssids; uint_t nelem, i; int found = 0; if ((err = nwam_known_wlan_get_prop_value(kwh, NWAM_KNOWN_WLAN_PROP_BSSIDS, &bssidsval)) != NWAM_SUCCESS) { nlog(LOG_ERR, "bssid_match: %s", nwam_strerror(err)); return (0); } if ((err = nwam_value_get_string_array(bssidsval, &bssids, &nelem)) != NWAM_SUCCESS) { nwam_value_free(bssidsval); return (0); } for (i = 0; i < nelem; i++) { if (strcmp((const char *)bssid, bssids[i]) == 0) { found = 1; break; } } nwam_value_free(bssidsval); return (found); } /* Find most prioritized AP with strongest signal in scan data. */ static int find_best_wlan_cb(nwam_known_wlan_handle_t kwh, void *data) { nwamd_ncu_t *ncu = data; nwamd_link_t *link = &ncu->ncu_link; nwamd_wifi_scan_t *s = &link->nwamd_link_wifi_scan; nwam_error_t err; char *name = NULL; int i; dladm_wlan_strength_t curr_strength = 0; dladm_wlan_strength_t max_strength = 0; boolean_t found = B_FALSE; if ((err = nwam_known_wlan_get_name(kwh, &name)) != NWAM_SUCCESS) { nlog(LOG_ERR, "find_best_wlan_cb: could not look up name: %s", nwam_strerror(err)); return (0); } if (link->nwamd_link_wifi_connected) { (void) dladm_wlan_str2strength (link->nwamd_link_wifi_signal_strength, &curr_strength); } /* * If we're >= scan level, don't pick another Known WLAN if still * connected (even if a Known WLAN with higher priority is available). * If the user wants to connect to a different Known WLAN, it can be * done from the GUI or select-wifi subcommand of nwamadm(8). */ if (curr_strength >= wireless_scan_level && link->nwamd_link_wifi_connected) { free(name); return (1); } for (i = 0; i < s->nwamd_wifi_scan_curr_num; i++) { nwam_wlan_t *cur_wlan = &(s->nwamd_wifi_scan_curr[i]); int b_match = bssid_match(kwh, cur_wlan->nww_bssid); /* * We need to either match the scanned essid, or in the case * where the essid was not broadcast, match the scanned bssid. */ if (strcmp(cur_wlan->nww_essid, name) != 0 && !(cur_wlan->nww_essid[0] == '\0' && b_match)) continue; /* * If wireless_strict_bssid is specified, need to match * BSSID too. */ if (wireless_strict_bssid && !b_match) continue; /* * Found a match. Since we walk known WLANs in * priority order, it's guaranteed to be the * most prioritized. It may not be the strongest though - * we continue the walk and record the strength along * with the ESSID and BSSID, so that if we encounter * another AP with the same ESSID but a higher signal strength, * we will choose it - but only if the currently-connected * WLAN is at or below wireless_scan_level. */ (void) dladm_wlan_str2strength (cur_wlan->nww_signal_strength, &curr_strength); if (curr_strength > max_strength) { (void) strlcpy(link->nwamd_link_wifi_essid, cur_wlan->nww_essid, sizeof (link->nwamd_link_wifi_essid)); /* * Set BSSID if wireless_strict_bssid is specified or * if this is a hidden WLAN. Store the BSSID here and * then later determine the hidden WLAN's name in the * connect thread. */ if (wireless_strict_bssid || cur_wlan->nww_essid[0] == '\0') { (void) strlcpy(link->nwamd_link_wifi_bssid, cur_wlan->nww_bssid, sizeof (link->nwamd_link_wifi_bssid)); } (void) strlcpy(link->nwamd_link_wifi_signal_strength, cur_wlan->nww_signal_strength, sizeof (link->nwamd_link_wifi_signal_strength)); link->nwamd_link_wifi_security_mode = cur_wlan->nww_security_mode; found = B_TRUE; } (void) dladm_wlan_str2strength (link->nwamd_link_wifi_signal_strength, &max_strength); } free(name); return (found ? 1 : 0); } static boolean_t nwamd_find_known_wlan(nwamd_object_t ncu_obj) { nwamd_ncu_t *ncu = ncu_obj->nwamd_object_data; int ret; /* * Walk known WLANs, finding lowest priority (preferred) WLAN * in our scan results. */ (void) nwam_walk_known_wlans(find_best_wlan_cb, ncu, NWAM_FLAG_KNOWN_WLAN_WALK_PRIORITY_ORDER, &ret); return (ret == 1); } /* * WLAN scan code for WIFI link NCUs. */ /* Create periodic scan event for object. Called with object lock held. */ void nwamd_ncu_create_periodic_scan_event(nwamd_object_t ncu_obj) { nwamd_event_t scan_event; if (wireless_scan_interval == 0) { nlog(LOG_DEBUG, "nwamd_ncu_create_periodic_scan_event: " "wireless_scan_interval set to 0 so no periodic scanning"); return; } scan_event = nwamd_event_init(NWAM_EVENT_TYPE_PERIODIC_SCAN, NWAM_OBJECT_TYPE_NCU, 0, ncu_obj->nwamd_object_name); if (scan_event != NULL) { nwamd_event_enqueue_timed(scan_event, wireless_scan_interval > WIRELESS_SCAN_INTERVAL_MIN ? wireless_scan_interval : WIRELESS_SCAN_INTERVAL_MIN); } } /* Handle periodic scan event (which puts link into WIFI_INIT state */ void nwamd_ncu_handle_periodic_scan_event(nwamd_event_t event) { nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (ncu_obj == NULL) { nlog(LOG_ERR, "nwamd_ncu_handle_periodic_scan_event: " "no object %s", event->event_object); return; } ncu = ncu_obj->nwamd_object_data; /* Only rescan if state is offline* or online */ nlog(LOG_DEBUG, "nwamd_ncu_handle_periodic_scan_event: doing rescan.."); if (ncu_obj->nwamd_object_state == NWAM_STATE_OFFLINE_TO_ONLINE || ncu_obj->nwamd_object_state == NWAM_STATE_ONLINE) { /* rescan, then create periodic scan event */ (void) nwamd_wlan_scan(ncu->ncu_name); nwamd_ncu_create_periodic_scan_event(ncu_obj); } nwamd_object_release(ncu_obj); } static boolean_t get_scan_results(void *arg, dladm_wlan_attr_t *attrp) { nwamd_wifi_scan_t *s = arg; const char *linkname = s->nwamd_wifi_scan_link; char essid_name[DLADM_STRSIZE]; char bssid_name[DLADM_STRSIZE]; char strength[DLADM_STRSIZE]; uint_t i, index = 0; boolean_t found = B_FALSE; (void) dladm_wlan_essid2str(&attrp->wa_essid, essid_name); (void) dladm_wlan_bssid2str(&attrp->wa_bssid, bssid_name); (void) dladm_wlan_strength2str(&attrp->wa_strength, strength); index = s->nwamd_wifi_scan_curr_num; if (index == NWAMD_MAX_NUM_WLANS) { nlog(LOG_ERR, "get_scan_results: truncating WLAN scan results " "for link %s: ommiting (%s, %s)", linkname, essid_name, bssid_name); return (B_TRUE); } (void) strlcpy(s->nwamd_wifi_scan_curr[index].nww_essid, essid_name, sizeof (s->nwamd_wifi_scan_curr[index].nww_essid)); (void) strlcpy(s->nwamd_wifi_scan_curr[index].nww_bssid, bssid_name, sizeof (s->nwamd_wifi_scan_curr[index].nww_bssid)); (void) strlcpy(s->nwamd_wifi_scan_curr[index].nww_signal_strength, strength, sizeof (s->nwamd_wifi_scan_curr[index].nww_signal_strength)); s->nwamd_wifi_scan_curr[index].nww_security_mode = attrp->wa_secmode; s->nwamd_wifi_scan_curr[index].nww_speed = attrp->wa_speed; s->nwamd_wifi_scan_curr[index].nww_channel = attrp->wa_channel; s->nwamd_wifi_scan_curr[index].nww_bsstype = attrp->wa_bsstype; /* * We fill in actual values for selected/connected/key later when we * reacquire the object lock. */ s->nwamd_wifi_scan_curr[index].nww_selected = B_FALSE; s->nwamd_wifi_scan_curr[index].nww_connected = B_FALSE; s->nwamd_wifi_scan_curr[index].nww_have_key = B_FALSE; s->nwamd_wifi_scan_curr[index].nww_keyindex = 1; s->nwamd_wifi_scan_curr_num++; /* Check if this AP was in previous scan results */ for (i = 0; i < s->nwamd_wifi_scan_last_num; i++) { found = (strcmp(s->nwamd_wifi_scan_last[i].nww_essid, essid_name) == 0 && strcmp(s->nwamd_wifi_scan_last[i].nww_bssid, bssid_name) == 0); if (found) break; } if (!found) s->nwamd_wifi_scan_changed = B_TRUE; nlog(LOG_DEBUG, "get_scan_results(%s, %d): ESSID %s, BSSID %s", linkname, index, essid_name, bssid_name); return (B_TRUE); } /* * Check if we're connected to the expected WLAN, or in the case of autoconf * record the WLAN we're connected to. */ boolean_t nwamd_wlan_connected(nwamd_object_t ncu_obj) { nwamd_ncu_t *ncu = ncu_obj->nwamd_object_data; nwamd_link_t *link = &ncu->ncu_link; dladm_wlan_linkattr_t attr; char essid[DLADM_STRSIZE]; char bssid[DLADM_STRSIZE]; boolean_t connected = B_FALSE; int retries = 0; /* * This is awful, but some wireless drivers * (particularly 'ath') will erroneously report * "disconnected" if queried right after a scan. If we * see 'down' reported here, we retry a few times to * make sure it's really down. */ while (retries++ < 4) { if (dladm_wlan_get_linkattr(dld_handle, link->nwamd_link_id, &attr) != DLADM_STATUS_OK) { attr.la_status = DLADM_WLAN_LINK_DISCONNECTED; } else if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) { break; } } if (attr.la_status == DLADM_WLAN_LINK_CONNECTED) { (void) dladm_wlan_essid2str(&attr.la_wlan_attr.wa_essid, essid); (void) dladm_wlan_bssid2str(&attr.la_wlan_attr.wa_bssid, bssid); connected = B_TRUE; nlog(LOG_DEBUG, "nwamd_wlan_connected: %s connected to %s %s", ncu->ncu_name, essid, bssid); } else { return (B_FALSE); } /* * If we're using autoconf, we have no control over what we connect to, * so rather than verifying ESSSID, simply record ESSID/BSSID. */ if (link->nwamd_link_wifi_autoconf) { (void) strlcpy(link->nwamd_link_wifi_essid, essid, sizeof (link->nwamd_link_wifi_essid)); (void) strlcpy(link->nwamd_link_wifi_bssid, bssid, sizeof (link->nwamd_link_wifi_bssid)); } /* * Are we connected to expected WLAN? Note: * we'd like to verify BSSID, but we cannot due to CR 6772510. */ if (strcmp(essid, link->nwamd_link_wifi_essid) == 0) { /* Update connected signal strength */ (void) dladm_wlan_strength2str(&attr.la_wlan_attr.wa_strength, link->nwamd_link_wifi_signal_strength); /* Store current BSSID */ (void) strlcpy(link->nwamd_link_wifi_bssid, bssid, sizeof (link->nwamd_link_wifi_bssid)); if (attr.la_wlan_attr.wa_strength < wireless_scan_level) { /* * We're connected, but we've dropped below * scan threshold. Initiate a scan. */ nlog(LOG_DEBUG, "nwamd_wlan_connected: " "connected but signal under threshold..."); (void) nwamd_wlan_scan(ncu->ncu_name); } return (connected); } else if (strlen(essid) == 0) { /* * For hidden WLANs, no ESSID is specified, so we cannot verify * WLAN name. */ nlog(LOG_DEBUG, "nwamd_wlan_connected: connected to hidden WLAN, cannot " "verify connection details"); return (connected); } else { (void) nlog(LOG_ERR, "nwamd_wlan_connected: wrong AP on %s; expected %s %s", ncu->ncu_name, link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid); (void) dladm_wlan_disconnect(dld_handle, link->nwamd_link_id); link->nwamd_link_wifi_connected = B_FALSE; return (B_FALSE); } } /* * WLAN scan thread. Called with the per-link WiFi mutex held. */ static void * wlan_scan_thread(void *arg) { char *linkname = arg; nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; nwamd_link_t *link; dladm_status_t status; char essid[DLADM_STRSIZE]; char bssid[DLADM_STRSIZE]; uint32_t now, link_id; nwamd_wifi_scan_t s; int i; if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname)) == NULL) { nlog(LOG_ERR, "wlan_scan_thread: could not find object " "for link %s", linkname); free(linkname); return (NULL); } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; /* * It is possible multiple scan threads have queued up waiting for the * object lock. We try to prevent excessive scanning by limiting the * interval between scans to WIRELESS_SCAN_REQUESTED_INTERVAL_MIN sec. */ now = NSEC_TO_SEC(gethrtime()); if ((now - link->nwamd_link_wifi_scan.nwamd_wifi_scan_last_time) < WIRELESS_SCAN_REQUESTED_INTERVAL_MIN) { nlog(LOG_DEBUG, "wlan_scan_thread: last scan for %s " "was < %d sec ago, ignoring scan request", linkname, WIRELESS_SCAN_REQUESTED_INTERVAL_MIN); nwamd_object_release(ncu_obj); free(linkname); return (NULL); } /* * Prepare scan data - copy link name and copy previous "current" * scan results from the nwamd_link_t to the last scan results for * the next scan so that we can compare results to find if things * have changed since last time. */ (void) bzero(&s, sizeof (nwamd_wifi_scan_t)); (void) strlcpy(s.nwamd_wifi_scan_link, ncu->ncu_name, sizeof (s.nwamd_wifi_scan_link)); s.nwamd_wifi_scan_last_num = link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr_num; if (s.nwamd_wifi_scan_last_num > 0) { (void) memcpy(s.nwamd_wifi_scan_last, link->nwamd_link_wifi_scan.nwamd_wifi_scan_curr, s.nwamd_wifi_scan_last_num * sizeof (nwam_wlan_t)); } link_id = link->nwamd_link_id; nwamd_object_release(ncu_obj); nlog(LOG_DEBUG, "wlan_scan_thread: initiating scan on %s", s.nwamd_wifi_scan_link); scanconnect_entry(); status = dladm_wlan_scan(dld_handle, link_id, &s, get_scan_results); s.nwamd_wifi_scan_last_time = NSEC_TO_SEC(gethrtime()); if (!s.nwamd_wifi_scan_changed) { /* Scan may have lost WLANs, if so this qualifies as change */ s.nwamd_wifi_scan_changed = (s.nwamd_wifi_scan_curr_num != s.nwamd_wifi_scan_last_num); } scanconnect_exit(); if (status != DLADM_STATUS_OK) { nlog(LOG_ERR, "wlan_scan_thread: cannot scan link %s", s.nwamd_wifi_scan_link); free(linkname); return (NULL); } if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname)) == NULL) { nlog(LOG_ERR, "wlan_scan_thread: could not find object " "for link %s after doing scan", linkname); free(linkname); return (NULL); } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; /* For new scan data, add key info from known WLANs */ for (i = 0; i < s.nwamd_wifi_scan_curr_num; i++) { if (NEED_ENC(s.nwamd_wifi_scan_curr[i].nww_security_mode)) { char keyname[NWAM_MAX_VALUE_LEN]; dladm_wlan_key_t *key = NULL; /* * If strict_bssid is true, we start checking for * known wlans with the same BSSID. * This would prevent the selection of secobjs * that actually are referenced by different kwl * with the same ESSID. */ if (wireless_strict_bssid) { int b_match = 0; (void) nwam_walk_known_wlans(bssid_match, s.nwamd_wifi_scan_curr[i].nww_bssid, 0, &b_match); if (b_match == 0) continue; } if (known_wlan_get_keyname (s.nwamd_wifi_scan_curr[i].nww_essid, keyname) == NWAM_SUCCESS && (key = nwamd_wlan_get_key_named(keyname, s.nwamd_wifi_scan_curr[i].nww_security_mode)) != NULL) { s.nwamd_wifi_scan_curr[i].nww_have_key = B_TRUE; s.nwamd_wifi_scan_curr[i].nww_keyindex = s.nwamd_wifi_scan_curr[i]. nww_security_mode == DLADM_WLAN_SECMODE_WEP ? key->wk_idx : 1; nlog(LOG_DEBUG, "found matching keyname for \ %s", s.nwamd_wifi_scan_curr[i].nww_bssid); free(key); } } } /* Copy scan data into nwamd_link_t */ link->nwamd_link_wifi_scan = s; /* Set selected, connected and send scan event if we've got new data */ nwamd_set_selected_connected(ncu, link->nwamd_link_wifi_essid[0] != '\0', link->nwamd_link_wifi_connected); /* * If wireless selection is not possible because of the current * state or priority-group, then this was just a scan request. * Nothing else to do. */ if (!wireless_selection_possible(ncu_obj)) { nwamd_object_release(ncu_obj); free(linkname); return (NULL); } /* * Check if WLAN is on our known WLAN list. If no * previously-visited WLANs are found in scan data, set * new state to NEED_SELECTION (provided we're not currently * connected, as can be the case during a periodic scan or * monitor-triggered scan where the signal strength recovers. */ if (!nwamd_find_known_wlan(ncu_obj)) { if (!nwamd_wlan_connected(ncu_obj)) { if (link->nwamd_link_wifi_connected) { nlog(LOG_DEBUG, "wlan_scan_thread: " "unexpected disconnect after scan"); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); } else { nlog(LOG_DEBUG, "wlan_scan_thread: " "no known WLANs - ask user"); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_LINK_WIFI_NEED_SELECTION); } } else { /* still connected. if not online, change to online */ nlog(LOG_DEBUG, "wlan_scan_thread: still connected to " "%s %s", link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid); if (ncu_obj->nwamd_object_state != NWAM_STATE_ONLINE) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_UP); } } nwamd_object_release(ncu_obj); } else { nlog(LOG_DEBUG, "wlan_scan_thread: found known WLAN %s %s", link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid); if (!nwamd_wlan_connected(ncu_obj)) { /* Copy selected ESSID/BSSID, unlock, call select */ (void) strlcpy(essid, link->nwamd_link_wifi_essid, sizeof (essid)); (void) strlcpy(bssid, link->nwamd_link_wifi_bssid, sizeof (bssid)); nwamd_object_release(ncu_obj); (void) nwamd_wlan_select(linkname, essid, bssid, link->nwamd_link_wifi_security_mode, B_TRUE); } else { /* still connected. if not online, change to online */ nlog(LOG_DEBUG, "wlan_scan_thread: still connected to " "known WLAN %s %s", link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid); if (ncu_obj->nwamd_object_state != NWAM_STATE_ONLINE) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_UP); } nwamd_object_release(ncu_obj); } } free(linkname); return (NULL); } nwam_error_t nwamd_wlan_scan(const char *linkname) { pthread_t wifi_thread; char *link = strdup(linkname); if (link == NULL) { nlog(LOG_ERR, "nwamd_wlan_scan: out of memory"); return (NWAM_NO_MEMORY); } nlog(LOG_DEBUG, "nwamd_wlan_scan: WLAN scan for %s", link); if (pthread_create(&wifi_thread, NULL, wlan_scan_thread, link) != 0) { nlog(LOG_ERR, "nwamd_wlan_scan: could not start scan"); free(link); return (NWAM_ERROR_INTERNAL); } /* detach thread so that it doesn't become a zombie */ (void) pthread_detach(wifi_thread); return (NWAM_SUCCESS); } /* * WLAN connection code. */ static dladm_status_t do_connect(uint32_t link_id, dladm_wlan_attr_t *attrp, dladm_wlan_key_t *key, uint_t keycount, uint_t flags) { dladm_status_t status; char errmsg[DLADM_STRSIZE]; scanconnect_entry(); status = dladm_wlan_connect(dld_handle, link_id, attrp, DLADM_WLAN_CONNECT_TIMEOUT_DEFAULT, key, keycount, flags); scanconnect_exit(); nlog(LOG_DEBUG, "nwamd_do_connect: dladm_wlan_connect returned %s", dladm_status2str(status, errmsg)); return (status); } static void * wlan_connect_thread(void *arg) { char *linkname = arg; nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; nwamd_link_t *link; nwam_error_t err; uint_t keycount; uint32_t link_id; dladm_wlan_key_t *key = NULL; dladm_wlan_attr_t attr; dladm_status_t status; boolean_t autoconf = B_FALSE; if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname)) == NULL) { nlog(LOG_ERR, "wlan_connect_thread: could not find object " "for link %s", linkname); free(linkname); return (NULL); } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; if (!wireless_selection_possible(ncu_obj)) { nlog(LOG_DEBUG, "wlan_connect_thread: %s in invalid state or " "has lower priority", ncu->ncu_name); goto done; } /* If it is already connected to the required AP, just return. */ if (nwamd_wlan_connected(ncu_obj)) { nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, ncu_obj->nwamd_object_state, NWAM_AUX_STATE_UP); goto done; } (void) memset(&attr, 0, sizeof (attr)); if (dladm_wlan_str2essid(link->nwamd_link_wifi_essid, &attr.wa_essid) != DLADM_STATUS_OK) { nlog(LOG_ERR, "wlan_connect_thread: invalid ESSID '%s' " "for '%s'", link->nwamd_link_wifi_essid, ncu->ncu_name); goto done; } attr.wa_valid = DLADM_WLAN_ATTR_ESSID; /* note: bssid logic here is non-functional */ if (link->nwamd_link_wifi_bssid[0] != '\0') { if (dladm_wlan_str2bssid(link->nwamd_link_wifi_bssid, &attr.wa_bssid) != DLADM_STATUS_OK) { nlog(LOG_ERR, "wlan_connect_thread: invalid BSSID '%s'", "for '%s'", link->nwamd_link_wifi_bssid, ncu->ncu_name); } else { attr.wa_valid |= DLADM_WLAN_ATTR_BSSID; } } /* First check for the key */ if (NEED_ENC(link->nwamd_link_wifi_security_mode)) { if (link->nwamd_link_wifi_key == NULL) { nlog(LOG_ERR, "wlan_connect_thread: could not find " "key for WLAN '%s'", link->nwamd_link_wifi_essid); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_LINK_WIFI_NEED_KEY); goto done; } /* Make a copy of the key as we need to unlock the object */ if ((key = calloc(1, sizeof (dladm_wlan_key_t))) == NULL) { nlog(LOG_ERR, "wlan_connect_thread: out of memory"); goto done; } (void) memcpy(key, link->nwamd_link_wifi_key, sizeof (dladm_wlan_key_t)); attr.wa_valid |= DLADM_WLAN_ATTR_SECMODE; attr.wa_secmode = link->nwamd_link_wifi_security_mode; keycount = 1; nlog(LOG_DEBUG, "wlan_connect_thread: retrieved key"); } else { key = NULL; keycount = 0; } /* * Connect; only scan if a bssid was not specified. If it times out, * try a second time using autoconf. Drop the object lock during the * connect attempt since connecting may take some time, and access to * the link object during that period would be impossible if we held the * lock. */ link->nwamd_link_wifi_autoconf = B_FALSE; link_id = link->nwamd_link_id; nwamd_object_release(ncu_obj); status = do_connect(link_id, &attr, key, keycount, DLADM_WLAN_CONNECT_NOSCAN); if (status != DLADM_STATUS_OK) { /* Connect failed, try autoconf */ if (!wireless_autoconf || (status = do_connect(link_id, &attr, NULL, 0, 0)) != DLADM_STATUS_OK) { nlog(LOG_ERR, "wlan_connect_thread: connect failed for " "%s", linkname); goto done_unlocked; } if (status == DLADM_STATUS_OK) autoconf = B_TRUE; } /* Connect succeeded, reacquire object */ if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname)) == NULL) { nlog(LOG_ERR, "wlan_connect_thread: could not find object " "for link %s", linkname); goto done_unlocked; } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; if (autoconf) link->nwamd_link_wifi_autoconf = B_TRUE; /* * If WLAN is WEP/WPA, we would like to test the connection as the key * may be wrong. It is difficult to find a reliable test that works * across APs however. Do nothing for now. */ link->nwamd_link_wifi_connected = nwamd_wlan_connected(ncu_obj); if (link->nwamd_link_wifi_connected) { if (link->nwamd_link_wifi_add_to_known_wlans) { /* add to known WLANs */ nlog(LOG_DEBUG, "wlan_connect_thread: " "add '%s' to known WLANs", link->nwamd_link_wifi_essid); if ((err = nwam_known_wlan_add_to_known_wlans (link->nwamd_link_wifi_essid, link->nwamd_link_wifi_bssid[0] != '\0' ? link->nwamd_link_wifi_bssid : NULL, link->nwamd_link_wifi_security_mode, link->nwamd_link_wifi_security_mode == DLADM_WLAN_SECMODE_WEP ? (uint_t)link->nwamd_link_wifi_key->wk_idx : 1, NEED_ENC(link->nwamd_link_wifi_security_mode) ? link->nwamd_link_wifi_keyname : NULL)) != NWAM_SUCCESS) { nlog(LOG_ERR, "wlan_connect_thread: " "could not add to known WLANs: %s", nwam_strerror(err)); } } nwamd_set_selected_connected(ncu, B_TRUE, B_TRUE); nlog(LOG_DEBUG, "wlan_connect_thread: connect " "succeeded, setting state online"); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE, NWAM_AUX_STATE_UP); } done: nwamd_object_release(ncu_obj); done_unlocked: free(linkname); free(key); return (NULL); } void nwamd_wlan_connect(const char *linkname) { pthread_t wifi_thread; char *link = strdup(linkname); if (link == NULL) { nlog(LOG_ERR, "nwamd_wlan_connect: out of memory"); return; } nlog(LOG_DEBUG, "nwamd_wlan_connect: WLAN connect for %s", link); if (pthread_create(&wifi_thread, NULL, wlan_connect_thread, link) != 0) nlog(LOG_ERR, "nwamd_wlan_connect: could not start connect"); /* detach thread so that it doesn't become a zombie */ (void) pthread_detach(wifi_thread); } /* * Launch signal strength-monitoring thread which periodically * checks connection and signal strength. If we become disconnected * or signal drops below threshold specified by wireless_scan_level, * initiate a scan. The scan initiation is taken care of by * the call to nwamd_wlan_connected(). */ static void * wlan_monitor_signal_thread(void *arg) { char *linkname = arg; nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; nwamd_link_t *link; boolean_t first_time = B_TRUE; for (;;) { if ((ncu_obj = nwamd_ncu_object_find(NWAM_NCU_TYPE_LINK, linkname)) == NULL) { nlog(LOG_ERR, "wlan_monitor_signal_thread: could " "not find object for link %s", linkname); break; } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; /* If the NCU is DISABLED/OFFLINE, exit the monitoring thread */ if (ncu_obj->nwamd_object_state == NWAM_STATE_OFFLINE || ncu_obj->nwamd_object_state == NWAM_STATE_DISABLED) { nlog(LOG_INFO, "wlan_monitor_signal_thread: " "%s is %s, stopping thread", linkname, nwam_state_to_string(ncu_obj->nwamd_object_state)); link->nwamd_link_wifi_monitor_thread = 0; nwamd_object_release(ncu_obj); break; } /* * First time thru loop, we check if there is another * link monitoring thread in operation - if so exit this * thread. */ if (first_time) { first_time = B_FALSE; if (link->nwamd_link_wifi_monitor_thread != 0) { /* Already have a monitor thread for link? */ nwamd_object_release(ncu_obj); break; } else { link->nwamd_link_wifi_monitor_thread = pthread_self(); } } if (!nwamd_wlan_connected(ncu_obj)) { nlog(LOG_ERR, "wlan_monitor_signal_thread: " "disconnect occured for WLAN on link %s", linkname); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, ncu_obj->nwamd_object_name, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); link->nwamd_link_wifi_monitor_thread = 0; nwamd_object_release(ncu_obj); break; } nwamd_object_release(ncu_obj); (void) sleep(WIRELESS_MONITOR_SIGNAL_INTERVAL); } free(linkname); return (NULL); } void nwamd_wlan_monitor_signal(const char *linkname) { pthread_t wifi_thread; char *link = strdup(linkname); if (link == NULL) { nlog(LOG_ERR, "nwamd_wlan_monitor_signal: out of memory"); return; } nlog(LOG_DEBUG, "nwamd_wlan_monitor_signal: WLAN monitor for %s", link); if (pthread_create(&wifi_thread, NULL, wlan_monitor_signal_thread, link) != 0) { nlog(LOG_ERR, "nwamd_wlan_monitor_signal: could not monitor " "link %s", link); free(link); return; } /* detach thread so that it doesn't become a zombie */ (void) pthread_detach(wifi_thread); } void nwamd_ncu_handle_link_state_event(nwamd_event_t event) { nwam_event_t evm; nwamd_object_t ncu_obj; nwamd_ncu_t *ncu; nwamd_link_t *link; ncu_obj = nwamd_object_find(NWAM_OBJECT_TYPE_NCU, event->event_object); if (ncu_obj == NULL) { nlog(LOG_INFO, "nwamd_ncu_handle_link_state_event: no object " "%s", event->event_object); nwamd_event_do_not_send(event); return; } ncu = ncu_obj->nwamd_object_data; link = &ncu->ncu_link; evm = event->event_msg; /* * We ignore link state events for WiFi because it is very flaky. * Instead we use the monitor thread and drive WiFi state changes from * there. */ if (link->nwamd_link_media == DL_WIFI) { nwamd_object_release(ncu_obj); return; } /* * If it's a link up event and we're not disabled, go online. */ if (evm->nwe_data.nwe_link_state.nwe_link_up && ncu_obj->nwamd_object_state != NWAM_STATE_DISABLED) { if (link->nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED) { int64_t priority_group; (void) pthread_mutex_lock(&active_ncp_mutex); priority_group = current_ncu_priority_group; (void) pthread_mutex_unlock(&active_ncp_mutex); /* compare priority groups */ if (link->nwamd_link_priority_group > priority_group) { nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: " "got LINK UP event for priority group " "%lld, less preferred than current %lld, " "ignoring", link->nwamd_link_priority_group, priority_group); } else if (link->nwamd_link_priority_group == priority_group) { nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: " "got LINK UP event for priority group " "%lld, same as current %lld", link->nwamd_link_priority_group, priority_group); /* * Change link state to UP. It will be * propagated to IP state machine. Only do * the NCU check if and when the interface * NCU is online. */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_UP); } else { nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: " "got LINK UP event for priority group " "%lld, more preferred than current %lld", link->nwamd_link_priority_group, priority_group); /* * We need to mark the link as up so that when * it is activated we will bring the interface * up. */ nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_UP); nwamd_object_release(ncu_obj); nwamd_ncp_deactivate_priority_group (priority_group); nwamd_ncp_activate_priority_group (link->nwamd_link_priority_group); return; } } else if (link->nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_MANUAL) { nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: " "got LINK UP event for manual NCU %s", ncu_obj->nwamd_object_name); nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_OFFLINE_TO_ONLINE, NWAM_AUX_STATE_UP); } } /* * If the link is down then start or continue transition down. */ if (!evm->nwe_data.nwe_link_state.nwe_link_up && (ncu_obj->nwamd_object_state == NWAM_STATE_ONLINE || ncu_obj->nwamd_object_state == NWAM_STATE_OFFLINE_TO_ONLINE)) { if (link->nwamd_link_activation_mode == NWAM_ACTIVATION_MODE_PRIORITIZED) { nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: " "got LINK DOWN for priority group %lld", link->nwamd_link_priority_group); /* Moving to offline checks priority group */ } else { nlog(LOG_DEBUG, "nwamd_ncu_handle_link_state_event: " "got LINK DOWN event for manual NCU %s", ncu_obj->nwamd_object_name); } nwamd_object_set_state(NWAM_OBJECT_TYPE_NCU, event->event_object, NWAM_STATE_ONLINE_TO_OFFLINE, NWAM_AUX_STATE_DOWN); } nwamd_object_release(ncu_obj); } /* * 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. */ #include #include #include #include #include #include #include #include #include #include "events.h" #include "objects.h" #include "util.h" /* * objects.c - contains routines which manipulate object lists of NCUs, * locations, ENMs and known WLANs. */ typedef struct nwamd_object_list { nwam_object_type_t object_type; uu_list_t *object_list; nwamd_event_method_t *object_event_methods; pthread_rwlock_t object_list_lock; } nwamd_object_list_t; nwamd_event_method_t enm_event_methods[] = { { NWAM_EVENT_TYPE_OBJECT_INIT, nwamd_enm_handle_init_event }, { NWAM_EVENT_TYPE_OBJECT_FINI, nwamd_enm_handle_fini_event }, { NWAM_EVENT_TYPE_OBJECT_ACTION, nwamd_enm_handle_action_event }, { NWAM_EVENT_TYPE_OBJECT_STATE, nwamd_enm_handle_state_event }, { NWAM_EVENT_TYPE_NOOP, NULL } }; nwamd_event_method_t loc_event_methods[] = { { NWAM_EVENT_TYPE_OBJECT_INIT, nwamd_loc_handle_init_event }, { NWAM_EVENT_TYPE_OBJECT_FINI, nwamd_loc_handle_fini_event }, { NWAM_EVENT_TYPE_OBJECT_ACTION, nwamd_loc_handle_action_event }, { NWAM_EVENT_TYPE_OBJECT_STATE, nwamd_loc_handle_state_event }, { NWAM_EVENT_TYPE_NOOP, NULL } }; nwamd_event_method_t ncu_event_methods[] = { { NWAM_EVENT_TYPE_IF_STATE, nwamd_ncu_handle_if_state_event }, { NWAM_EVENT_TYPE_IF_ACTION, nwamd_ncu_handle_if_action_event }, { NWAM_EVENT_TYPE_LINK_STATE, nwamd_ncu_handle_link_state_event }, { NWAM_EVENT_TYPE_LINK_ACTION, nwamd_ncu_handle_link_action_event }, { NWAM_EVENT_TYPE_OBJECT_INIT, nwamd_ncu_handle_init_event }, { NWAM_EVENT_TYPE_OBJECT_FINI, nwamd_ncu_handle_fini_event }, { NWAM_EVENT_TYPE_OBJECT_ACTION, nwamd_ncu_handle_action_event }, { NWAM_EVENT_TYPE_OBJECT_STATE, nwamd_ncu_handle_state_event }, { NWAM_EVENT_TYPE_PERIODIC_SCAN, nwamd_ncu_handle_periodic_scan_event }, { NWAM_EVENT_TYPE_NOOP, NULL } }; nwamd_event_method_t ncp_event_methods[] = { { NWAM_EVENT_TYPE_OBJECT_ACTION, nwamd_ncp_handle_action_event }, { NWAM_EVENT_TYPE_OBJECT_STATE, nwamd_ncp_handle_state_event }, { NWAM_EVENT_TYPE_UPGRADE, nwamd_handle_upgrade }, { NWAM_EVENT_TYPE_NOOP, NULL } }; nwamd_event_method_t known_wlan_event_methods[] = { { NWAM_EVENT_TYPE_OBJECT_INIT, nwamd_known_wlan_handle_init_event }, { NWAM_EVENT_TYPE_OBJECT_FINI, NULL }, { NWAM_EVENT_TYPE_OBJECT_ACTION, nwamd_known_wlan_handle_action_event }, { NWAM_EVENT_TYPE_NOOP, NULL } }; /* Should be kept in same order as object types */ nwamd_object_list_t object_lists[] = { { NWAM_OBJECT_TYPE_NCP, NULL, ncp_event_methods, PTHREAD_RWLOCK_INITIALIZER }, { NWAM_OBJECT_TYPE_NCU, NULL, ncu_event_methods, PTHREAD_RWLOCK_INITIALIZER }, { NWAM_OBJECT_TYPE_LOC, NULL, loc_event_methods, PTHREAD_RWLOCK_INITIALIZER }, { NWAM_OBJECT_TYPE_ENM, NULL, enm_event_methods, PTHREAD_RWLOCK_INITIALIZER }, { NWAM_OBJECT_TYPE_KNOWN_WLAN, NULL, known_wlan_event_methods, PTHREAD_RWLOCK_INITIALIZER } }; uu_list_pool_t *object_list_pool = NULL; /* * Comparison function for objects, passed in as callback to * uu_list_pool_create(). */ /* ARGSUSED */ static int nwamd_object_compare(const void *l_arg, const void *r_arg, void *private) { nwamd_object_t l = (nwamd_object_t)l_arg; nwamd_object_t r = (nwamd_object_t)r_arg; int rv; (void) pthread_mutex_lock(&l->nwamd_object_mutex); if (l != r) (void) pthread_mutex_lock(&r->nwamd_object_mutex); rv = strcmp(l->nwamd_object_name, r->nwamd_object_name); if (l != r) (void) pthread_mutex_unlock(&r->nwamd_object_mutex); (void) pthread_mutex_unlock(&l->nwamd_object_mutex); return (rv); } void nwamd_object_lists_init(void) { int i; object_list_pool = uu_list_pool_create("object_list_pool", sizeof (struct nwamd_object), offsetof(struct nwamd_object, nwamd_object_node), nwamd_object_compare, UU_LIST_POOL_DEBUG); if (object_list_pool == NULL) pfail("uu_list_pool_create failed with error %d", uu_error()); for (i = 0; i < sizeof (object_lists) / sizeof (struct nwamd_object_list); i++) { object_lists[i].object_list = uu_list_create(object_list_pool, NULL, 0); if (object_lists[i].object_list == NULL) pfail("uu_list_create failed with error %d", uu_error()); } } void nwamd_object_lists_fini(void) { int i; nwamd_object_t object; void *cookie = NULL; for (i = 0; i < sizeof (object_lists) / sizeof (struct nwamd_object_list); i++) { while ((object = uu_list_teardown(object_lists[i].object_list, &cookie)) != NULL) { free(object); } uu_list_destroy(object_lists[i].object_list); } if (object_list_pool != NULL) uu_list_pool_destroy(object_list_pool); } static nwamd_object_list_t * nwamd_get_object_list(nwam_object_type_t type) { assert(type < sizeof (object_lists) / sizeof (object_lists[0])); return (&object_lists[type]); } static int nwamd_object_list_lock(nwam_object_type_t type) { nwamd_object_list_t *object_list = nwamd_get_object_list(type); (void) pthread_rwlock_wrlock(&object_list->object_list_lock); return (0); } static int nwamd_object_list_rlock(nwam_object_type_t type) { nwamd_object_list_t *object_list = nwamd_get_object_list(type); if (pthread_rwlock_rdlock(&object_list->object_list_lock) == -1) { nlog(LOG_ERR, "cannot get lock for object list: %s", strerror(errno)); return (-1); } return (0); } static void nwamd_object_list_unlock(nwam_object_type_t type) { nwamd_object_list_t *object_list = nwamd_get_object_list(type); (void) pthread_rwlock_unlock(&object_list->object_list_lock); } /* * Initialize object and return it in locked state. */ nwamd_object_t nwamd_object_init(nwam_object_type_t type, const char *name, void *handle, void *data) { nwamd_object_t object; struct nwamd_object_list *object_list = nwamd_get_object_list(type); object = calloc(1, sizeof (struct nwamd_object)); if (object == NULL) return (NULL); (void) strlcpy(object->nwamd_object_name, name, NWAM_MAX_NAME_LEN); /* 1 for the list and 1 for the returned object */ object->nwamd_object_refcount = 2; object->nwamd_object_handle = handle; object->nwamd_object_data = data; object->nwamd_object_type = type; object->nwamd_object_state = NWAM_STATE_INITIALIZED; object->nwamd_object_aux_state = NWAM_AUX_STATE_INITIALIZED; /* Add object to appropriate object list */ if (nwamd_object_list_lock(type) != 0) { nlog(LOG_ERR, "nwamd_object_init: could not lock list to init " "object %s", name); free(object); return (NULL); } if (pthread_mutex_init(&object->nwamd_object_mutex, NULL) == -1) { nlog(LOG_ERR, "pthread_mutex_init failed: %s", strerror(errno)); free(object); nwamd_object_list_unlock(type); return (NULL); } (void) pthread_mutex_lock(&object->nwamd_object_mutex); uu_list_node_init(object, &object->nwamd_object_node, object_list_pool); (void) uu_list_insert_after(object_list->object_list, uu_list_last(object_list->object_list), object); nwamd_object_list_unlock(type); return (object); } /* * Find object in object list, returning it holding a lock and with the * reference count incremented. The opposite function to this is * nwamd_object_release(). */ nwamd_object_t nwamd_object_find(nwam_object_type_t type, const char *name) { nwamd_object_t object; struct nwamd_object_list *object_list = nwamd_get_object_list(type); assert(name != NULL); if (nwamd_object_list_rlock(type) != 0) return (NULL); for (object = uu_list_first(object_list->object_list); object != NULL; object = uu_list_next(object_list->object_list, object)) { if (strcmp(object->nwamd_object_name, name) == 0) break; } if (object != NULL) { (void) pthread_mutex_lock(&object->nwamd_object_mutex); object->nwamd_object_refcount++; } nwamd_object_list_unlock(type); return (object); } /* Removes object from list, destroy mutex, and free storage. */ static void nwamd_object_fini(nwamd_object_t object, nwam_object_type_t objtype) { nwamd_object_t o; struct nwamd_object_list *object_list; assert(object != NULL); object_list = nwamd_get_object_list(objtype); for (o = uu_list_first(object_list->object_list); o != NULL; o = uu_list_next(object_list->object_list, o)) { if (o == object) { uu_list_remove(object_list->object_list, object); (void) pthread_mutex_unlock( &object->nwamd_object_mutex); (void) pthread_mutex_destroy( &object->nwamd_object_mutex); uu_list_node_fini(object, &object->nwamd_object_node, object_list_pool); switch (objtype) { case NWAM_OBJECT_TYPE_NCU: nwamd_ncu_free(object->nwamd_object_data); nwam_ncu_free(object->nwamd_object_handle); break; case NWAM_OBJECT_TYPE_LOC: nwam_loc_free(object->nwamd_object_handle); break; case NWAM_OBJECT_TYPE_ENM: nwam_enm_free(object->nwamd_object_handle); break; default: nlog(LOG_ERR, "nwamd_object_fini: " "got unexpected object type %d", objtype); break; } free(object); break; } } } static void nwamd_object_decref(nwamd_object_t object, int num) { nwam_object_type_t objtype; assert(object->nwamd_object_refcount >= num); object->nwamd_object_refcount -= num; if (object->nwamd_object_refcount == 0) { /* * We need to maintain the locking hierarchy of owning the * list lock before we get the object lock when we are * destroying the object. If we merely release and then * reacquire in the right order we might not find the right * object. Instead we bump the ref count so that it can't * be destroyed, we drop the object lock, we acquire the * list lock, we acquire the object lock, decrement the ref * count, check to make sure we are really destroying it and * somebody else hasn't gotten it, and then, if its unref'd, * destroying it. */ object->nwamd_object_refcount++; objtype = object->nwamd_object_type; (void) pthread_mutex_unlock(&object->nwamd_object_mutex); (void) nwamd_object_list_lock(objtype); (void) pthread_mutex_lock(&object->nwamd_object_mutex); if (--object->nwamd_object_refcount != 0) (void) pthread_mutex_unlock( &object->nwamd_object_mutex); else nwamd_object_fini(object, objtype); nwamd_object_list_unlock(objtype); } else { (void) pthread_mutex_unlock(&object->nwamd_object_mutex); } } /* * Drop mutex without decreasing reference count. Used where we wish to * let go of an object but ensure it will not go away. */ void nwamd_object_release_and_preserve(nwamd_object_t object) { (void) pthread_mutex_unlock(&object->nwamd_object_mutex); } void nwamd_object_release(nwamd_object_t object) { nwamd_object_decref(object, 1); } void nwamd_object_release_and_destroy(nwamd_object_t object) { nwamd_object_decref(object, 2); } void nwamd_object_release_and_destroy_after_preserve(nwamd_object_t object) { nwamd_object_decref(object, 3); } void nwamd_object_release_after_preserve(nwamd_object_t object) { nwamd_object_decref(object, 2); } void nwamd_object_set_state_timed(nwam_object_type_t type, const char *name, nwam_state_t state, nwam_aux_state_t aux_state, uint32_t when) { nwamd_event_t event = nwamd_event_init_object_state(type, name, state, aux_state); nlog(LOG_INFO, "nwamd_object_set_state: state event (%s, %s) for %s", nwam_state_to_string(state), nwam_aux_state_to_string(aux_state), name); if (event != NULL) nwamd_event_enqueue_timed(event, when); } void nwamd_object_set_state(nwam_object_type_t type, const char *name, nwam_state_t state, nwam_aux_state_t aux_state) { nwamd_object_set_state_timed(type, name, state, aux_state, 0); } nwamd_event_method_t * nwamd_object_event_methods(nwam_object_type_t type) { struct nwamd_object_list *object_list = nwamd_get_object_list(type); return (object_list->object_event_methods); } /* * Walk all objects of specified type calling callback function cb. * Object is locked for duration of callback. */ int nwamd_walk_objects(nwam_object_type_t type, int (*cb)(nwamd_object_t, void *), void *data) { nwamd_object_t object; struct nwamd_object_list *object_list = nwamd_get_object_list(type); int ret = 0; if (nwamd_object_list_rlock(type) != 0) return (-1); for (object = uu_list_first(object_list->object_list); object != NULL; object = uu_list_next(object_list->object_list, object)) { (void) pthread_mutex_lock(&object->nwamd_object_mutex); ret = cb(object, data); (void) pthread_mutex_unlock(&object->nwamd_object_mutex); if (ret != 0) { nwamd_object_list_unlock(type); return (ret); } } nwamd_object_list_unlock(type); 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. */ #ifndef _OBJECTS_H #define _OBJECTS_H #include #include #include #include #include #include "events.h" #include "ncp.h" #include "ncu.h" /* * Wrapper structure for libnwam object, containing name, type, * associated object handle and optional object data field, and uu_list_node. */ struct nwamd_object { char nwamd_object_name[NWAM_MAX_NAME_LEN]; nwam_object_type_t nwamd_object_type; /* * These two elements provide a reference count for the structure and * a lock for the data including reference count. */ int nwamd_object_refcount; pthread_mutex_t nwamd_object_mutex; void *nwamd_object_handle; /* can point at ENMs, locations, etc. */ nwamd_ncu_t *nwamd_object_data; struct timeval nwamd_script_time; nwam_state_t nwamd_object_state; nwam_aux_state_t nwamd_object_aux_state; uu_list_node_t nwamd_object_node; }; /* Object init/enqueueing */ extern void nwamd_object_lists_init(void); extern void nwamd_object_lists_fini(void); extern nwamd_object_t nwamd_object_init(nwam_object_type_t, const char *, void *, void *); extern nwamd_object_t nwamd_object_find(nwam_object_type_t, const char *); extern void nwamd_object_release_and_preserve(nwamd_object_t); extern void nwamd_object_release(nwamd_object_t); extern void nwamd_object_release_and_destroy(nwamd_object_t); extern void nwamd_object_release_after_preserve(nwamd_object_t); extern void nwamd_object_release_and_destroy_after_preserve(nwamd_object_t); extern void nwamd_object_set_state(nwam_object_type_t, const char *, nwam_state_t, nwam_aux_state_t); extern void nwamd_object_set_state_timed(nwam_object_type_t, const char *, nwam_state_t, nwam_aux_state_t, uint32_t); extern nwamd_event_method_t *nwamd_object_event_methods(nwam_object_type_t); extern int nwamd_walk_objects(nwam_object_type_t, int (*)(nwamd_object_t, void *), void *); extern int nwamd_object_update(const char *, nwam_object_type_t); /* Known WLAN functions (no wlan objects, so no init/fini functions) */ /* event methods */ extern void nwamd_known_wlan_handle_init_event(nwamd_event_t); /* refresh/destroy a known WLAN */ extern int nwamd_known_wlan_action(const char *, nwam_action_t); /* ENM functions */ /* Init/fini functions for ENMs */ extern void nwamd_init_enms(void); extern void nwamd_fini_enms(void); /* ENM condition check function */ extern void nwamd_enm_check_conditions(void); /* event methods */ extern void nwamd_enm_handle_init_event(nwamd_event_t); extern void nwamd_enm_handle_fini_event(nwamd_event_t); /* enable/disable an enm */ extern int nwamd_enm_action(const char *, nwam_action_t); /* reread an enm from the repository */ extern int nwamd_enm_refresh(const char *); /* loc functions */ /* Init/fini functions for locs */ extern void nwamd_init_locs(void); extern void nwamd_fini_locs(void); /* loc condition check function */ extern void nwamd_loc_check_conditions(void); /* on shutdown, revert to legacy location */ extern void nwamd_loc_revert_to_legacy(void); /* event methods */ extern void nwamd_loc_handle_init_event(nwamd_event_t); extern void nwamd_loc_handle_fini_event(nwamd_event_t); /* enable/disable a loc */ extern int nwamd_loc_action(const char *, nwam_action_t); /* reread a loc from the repository */ extern int nwamd_loc_refresh(const char *); /* NCU functions */ extern void nwamd_init_ncus(void); extern void nwamd_fini_ncus(void); /* enable an ncp/ncu */ extern int nwamd_ncp_action(const char *, nwam_action_t); extern int nwamd_ncu_action(const char *, const char *, nwam_action_t); /* * Event callbacks. */ extern void nwamd_ncu_handle_init_event(nwamd_event_t); extern void nwamd_ncu_handle_fini_event(nwamd_event_t); extern void nwamd_ncu_handle_if_state_event(nwamd_event_t); extern void nwamd_ncu_handle_if_action_event(nwamd_event_t); extern void nwamd_ncu_handle_link_state_event(nwamd_event_t); extern void nwamd_ncu_handle_link_action_event(nwamd_event_t); extern void nwamd_ncu_handle_init_event(nwamd_event_t); extern void nwamd_ncu_handle_fini_event(nwamd_event_t); extern void nwamd_ncu_handle_action_event(nwamd_event_t); extern void nwamd_ncu_handle_state_event(nwamd_event_t); extern void nwamd_ncp_handle_action_event(nwamd_event_t); extern void nwamd_ncp_handle_state_event(nwamd_event_t); extern void nwamd_ncu_handle_periodic_scan_event(nwamd_event_t); extern void nwamd_ncp_handle_enable_event(nwamd_event_t); extern void nwamd_handle_upgrade(nwamd_event_t); extern void nwamd_enm_handle_action_event(nwamd_event_t); extern void nwamd_enm_handle_state_event(nwamd_event_t); extern void nwamd_loc_handle_action_event(nwamd_event_t); extern void nwamd_loc_handle_state_event(nwamd_event_t); extern void nwamd_known_wlan_handle_action_event(nwamd_event_t); extern void nwamd_add_phys_ncu_auto(nwam_ncp_handle_t, const char *); extern void nwamd_rem_phys_ncu_auto(nwam_ncp_handle_t, const char *); extern void add_auto_link(nwam_ncp_handle_t, const char *); extern void add_auto_ip(nwam_ncp_handle_t, const char *); extern void rem_auto_link(nwam_ncp_handle_t, const char *); extern void rem_auto_ip(nwam_ncp_handle_t, const char *); #endif /* _OBJECTS_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) 2010, Oracle and/or its affiliates. All rights reserved. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "events.h" #include "ncp.h" #include "ncu.h" #include "util.h" /* * routing_events.c - this file contains routines to retrieve routing socket * events and package them for high level processing. */ #define RTMBUFSZ sizeof (struct rt_msghdr) + \ (RTAX_MAX * sizeof (struct sockaddr_storage)) static void printaddrs(int, void *); static char *printaddr(void **); static void *getaddr(int, int, void *); static void setaddr(int, int *, void *, struct sockaddr *); union rtm_buf { /* Routing information. */ struct { struct rt_msghdr rtm; struct sockaddr_storage addr[RTAX_MAX]; } r; /* Interface information. */ struct { struct if_msghdr ifm; struct sockaddr_storage addr[RTAX_MAX]; } im; /* Interface address information. */ struct { struct ifa_msghdr ifa; struct sockaddr_storage addr[RTAX_MAX]; } ia; }; static int v4_sock = -1; static int v6_sock = -1; static pthread_t v4_routing, v6_routing; static int seq = 0; static const char * rtmtype_str(int type) { static char typestr[12]; /* strlen("type ") + enough for an int */ switch (type) { case RTM_NEWADDR: return ("NEWADDR"); case RTM_DELADDR: return ("DELADDR"); case RTM_CHGADDR: return ("CHGADDR"); case RTM_FREEADDR: return ("FREEADDR"); default: (void) snprintf(typestr, sizeof (typestr), "type %d", type); return (typestr); } } /* ARGSUSED0 */ static void * routing_events_v4(void *arg) { int n; union rtm_buf buffer; struct rt_msghdr *rtm; struct ifa_msghdr *ifa; char *addrs, *if_name; struct sockaddr_dl *addr_dl; struct sockaddr *addr, *netmask; nwamd_event_t ip_event; nlog(LOG_DEBUG, "v4 routing socket %d", v4_sock); for (;;) { rtm = &buffer.r.rtm; n = read(v4_sock, &buffer, sizeof (buffer)); if (n == -1 && errno == EAGAIN) { continue; } else if (n == -1) { nlog(LOG_ERR, "error reading routing socket " "%d: %m", v4_sock); /* Low likelihood. What's recovery path? */ continue; } if (rtm->rtm_msglen < n) { nlog(LOG_ERR, "only read %d bytes from " "routing socket but message claims to be " "of length %d", rtm->rtm_msglen); continue; } if (rtm->rtm_version != RTM_VERSION) { nlog(LOG_ERR, "tossing routing message of " "version %d type %d", rtm->rtm_version, rtm->rtm_type); continue; } if (rtm->rtm_msglen != n) { nlog(LOG_DEBUG, "routing message of %d size came from " "read of %d on socket %d", rtm->rtm_msglen, n, v4_sock); } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: case RTM_CHGADDR: case RTM_FREEADDR: ifa = (void *)rtm; addrs = (char *)ifa + sizeof (*ifa); nlog(LOG_DEBUG, "v4 routing message %s: " "index %d flags %x", rtmtype_str(rtm->rtm_type), ifa->ifam_index, ifa->ifam_flags); if ((addr = (struct sockaddr *)getaddr(RTA_IFA, ifa->ifam_addrs, addrs)) == NULL) break; /* Ignore routing socket messages for 0.0.0.0 */ /*LINTED*/ if (((struct sockaddr_in *)addr)->sin_addr.s_addr == INADDR_ANY) { nlog(LOG_DEBUG, "routing_events_v4: " "tossing message for 0.0.0.0"); break; } if ((netmask = (struct sockaddr *)getaddr(RTA_NETMASK, ifa->ifam_addrs, addrs)) == NULL) break; if ((addr_dl = (struct sockaddr_dl *)getaddr (RTA_IFP, ifa->ifam_addrs, addrs)) == NULL) break; /* * We don't use the lladdr in this structure so we can * run over it. */ addr_dl->sdl_data[addr_dl->sdl_nlen] = 0; if_name = addr_dl->sdl_data; /* no lifnum */ if (ifa->ifam_index == 0) { nlog(LOG_DEBUG, "tossing index 0 message"); break; } if (ifa->ifam_type != rtm->rtm_type) { nlog(LOG_INFO, "routing_events_v4: unhandled type %d", ifa->ifam_type); break; } printaddrs(ifa->ifam_addrs, addrs); /* Create and enqueue IF_STATE event */ ip_event = nwamd_event_init_if_state(if_name, ifa->ifam_flags, (rtm->rtm_type == RTM_NEWADDR || rtm->rtm_type == RTM_CHGADDR ? B_TRUE : B_FALSE), addr, netmask); if (ip_event != NULL) nwamd_event_enqueue(ip_event); break; } } /* NOTREACHED */ return (NULL); } /* ARGSUSED0 */ static void * routing_events_v6(void *arg) { int n; union rtm_buf buffer; struct rt_msghdr *rtm; struct ifa_msghdr *ifa; char *addrs, *if_name; struct sockaddr_dl *addr_dl; struct sockaddr *addr, *netmask; nwamd_event_t ip_event; nlog(LOG_DEBUG, "v6 routing socket %d", v6_sock); for (;;) { rtm = &buffer.r.rtm; n = read(v6_sock, &buffer, sizeof (buffer)); if (n == -1 && errno == EAGAIN) { continue; } else if (n == -1) { nlog(LOG_ERR, "error reading routing socket " "%d: %m", v6_sock); /* Low likelihood. What's recovery path? */ continue; } if (rtm->rtm_msglen < n) { nlog(LOG_ERR, "only read %d bytes from " "routing socket but message claims to be " "of length %d", rtm->rtm_msglen); continue; } if (rtm->rtm_version != RTM_VERSION) { nlog(LOG_ERR, "tossing routing message of " "version %d type %d", rtm->rtm_version, rtm->rtm_type); continue; } if (rtm->rtm_msglen != n) { nlog(LOG_DEBUG, "routing message of %d size came from " "read of %d on socket %d", rtm->rtm_msglen, n, v6_sock); } switch (rtm->rtm_type) { case RTM_NEWADDR: case RTM_DELADDR: case RTM_CHGADDR: case RTM_FREEADDR: ifa = (void *)rtm; addrs = (char *)ifa + sizeof (*ifa); nlog(LOG_DEBUG, "v6 routing message %s: " "index %d flags %x", rtmtype_str(rtm->rtm_type), ifa->ifam_index, ifa->ifam_flags); if ((addr = (struct sockaddr *)getaddr(RTA_IFA, ifa->ifam_addrs, addrs)) == NULL) break; /* Ignore routing socket messages for :: & linklocal */ /*LINTED*/ if (IN6_IS_ADDR_UNSPECIFIED( &((struct sockaddr_in6 *)addr)->sin6_addr)) { nlog(LOG_INFO, "routing_events_v6: " "tossing message for ::"); break; } /*LINTED*/ if (IN6_IS_ADDR_LINKLOCAL( &((struct sockaddr_in6 *)addr)->sin6_addr)) { nlog(LOG_INFO, "routing_events_v6: " "tossing message for link local address"); break; } if ((netmask = (struct sockaddr *)getaddr(RTA_NETMASK, ifa->ifam_addrs, addrs)) == NULL) break; if ((addr_dl = (struct sockaddr_dl *)getaddr (RTA_IFP, ifa->ifam_addrs, addrs)) == NULL) break; /* * We don't use the lladdr in this structure so we can * run over it. */ addr_dl->sdl_data[addr_dl->sdl_nlen] = 0; if_name = addr_dl->sdl_data; /* no lifnum */ if (ifa->ifam_index == 0) { nlog(LOG_DEBUG, "tossing index 0 message"); break; } if (ifa->ifam_type != rtm->rtm_type) { nlog(LOG_DEBUG, "routing_events_v6: unhandled type %d", ifa->ifam_type); break; } printaddrs(ifa->ifam_addrs, addrs); /* Create and enqueue IF_STATE event */ ip_event = nwamd_event_init_if_state(if_name, ifa->ifam_flags, (rtm->rtm_type == RTM_NEWADDR || rtm->rtm_type == RTM_CHGADDR ? B_TRUE : B_FALSE), addr, netmask); if (ip_event != NULL) nwamd_event_enqueue(ip_event); break; } } /* NOTREACHED */ return (NULL); } void nwamd_routing_events_init(void) { pthread_attr_t attr; /* * Initialize routing sockets here so that we know the routing threads * (and any requests to add a route) will be working with a valid socket * by the time we start handling events. */ v4_sock = socket(AF_ROUTE, SOCK_RAW, AF_INET); if (v4_sock == -1) pfail("failed to open v4 routing socket: %m"); v6_sock = socket(AF_ROUTE, SOCK_RAW, AF_INET6); if (v6_sock == -1) pfail("failed to open v6 routing socket: %m"); (void) pthread_attr_init(&attr); (void) pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (pthread_create(&v4_routing, &attr, routing_events_v4, NULL) != 0 || pthread_create(&v6_routing, &attr, routing_events_v6, NULL) != 0) pfail("routing thread creation failed"); (void) pthread_attr_destroy(&attr); } void nwamd_routing_events_fini(void) { (void) pthread_cancel(v4_routing); (void) pthread_cancel(v6_routing); } void nwamd_add_route(struct sockaddr *dest, struct sockaddr *mask, struct sockaddr *gateway, const char *ifname) { char rtbuf[RTMBUFSZ]; /* LINTED E_BAD_PTR_CAST_ALIGN */ struct rt_msghdr *rtm = (struct rt_msghdr *)rtbuf; void *addrs = rtbuf + sizeof (struct rt_msghdr); struct sockaddr_dl sdl; int rlen, index; int af; af = gateway->sa_family; /* retrieve the index value for the interface */ if ((index = if_nametoindex(ifname)) == 0) { nlog(LOG_ERR, "nwamd_add_route: if_nametoindex failed on %s", ifname); return; } (void) bzero(&sdl, sizeof (struct sockaddr_dl)); sdl.sdl_family = AF_LINK; sdl.sdl_index = index; (void) bzero(rtm, RTMBUFSZ); rtm->rtm_pid = getpid(); rtm->rtm_type = RTM_ADD; rtm->rtm_flags = RTF_UP | RTF_STATIC | RTF_GATEWAY; rtm->rtm_version = RTM_VERSION; rtm->rtm_seq = ++seq; rtm->rtm_msglen = sizeof (rtbuf); setaddr(RTA_DST, &rtm->rtm_addrs, &addrs, dest); setaddr(RTA_GATEWAY, &rtm->rtm_addrs, &addrs, gateway); setaddr(RTA_NETMASK, &rtm->rtm_addrs, &addrs, mask); setaddr(RTA_IFP, &rtm->rtm_addrs, &addrs, (struct sockaddr *)&sdl); if ((rlen = write(af == AF_INET ? v4_sock : v6_sock, rtbuf, rtm->rtm_msglen)) < 0) { nlog(LOG_ERR, "nwamd_add_route: " "got error %s writing to routing socket", strerror(errno)); } else if (rlen < rtm->rtm_msglen) { nlog(LOG_ERR, "nwamd_add_route: " "only wrote %d bytes of %d to routing socket\n", rlen, rtm->rtm_msglen); } } static char * printaddr(void **address) { static char buffer[80]; sa_family_t family = *(sa_family_t *)*address; struct sockaddr_in *s4 = *address; struct sockaddr_in6 *s6 = *address; struct sockaddr_dl *dl = *address; switch (family) { case AF_UNSPEC: (void) inet_ntop(AF_UNSPEC, &s4->sin_addr, buffer, sizeof (buffer)); *address = (char *)*address + sizeof (*s4); break; case AF_INET: (void) inet_ntop(AF_INET, &s4->sin_addr, buffer, sizeof (buffer)); *address = (char *)*address + sizeof (*s4); break; case AF_INET6: (void) inet_ntop(AF_INET6, &s6->sin6_addr, buffer, sizeof (buffer)); *address = (char *)*address + sizeof (*s6); break; case AF_LINK: (void) snprintf(buffer, sizeof (buffer), "link %.*s", dl->sdl_nlen, dl->sdl_data); *address = (char *)*address + sizeof (*dl); break; default: /* * We can't reliably update the size of this thing * because we don't know what its type is. So bump * it by a sockaddr_in and see what happens. The * caller should really make sure this never happens. */ *address = (char *)*address + sizeof (*s4); (void) snprintf(buffer, sizeof (buffer), "unknown address family %d", family); break; } return (buffer); } static void printaddrs(int mask, void *address) { if (mask == 0) return; if (mask & RTA_DST) nlog(LOG_DEBUG, "destination address: %s", printaddr(&address)); if (mask & RTA_GATEWAY) nlog(LOG_DEBUG, "gateway address: %s", printaddr(&address)); if (mask & RTA_NETMASK) nlog(LOG_DEBUG, "netmask: %s", printaddr(&address)); if (mask & RTA_GENMASK) nlog(LOG_DEBUG, "cloning mask: %s", printaddr(&address)); if (mask & RTA_IFP) nlog(LOG_DEBUG, "interface name: %s", printaddr(&address)); if (mask & RTA_IFA) nlog(LOG_DEBUG, "interface address: %s", printaddr(&address)); if (mask & RTA_AUTHOR) nlog(LOG_DEBUG, "author: %s", printaddr(&address)); if (mask & RTA_BRD) nlog(LOG_DEBUG, "broadcast address: %s", printaddr(&address)); } static void nextaddr(void **address) { sa_family_t family = *(sa_family_t *)*address; switch (family) { case AF_UNSPEC: case AF_INET: *address = (char *)*address + sizeof (struct sockaddr_in); break; case AF_INET6: *address = (char *)*address + sizeof (struct sockaddr_in6); break; case AF_LINK: *address = (char *)*address + sizeof (struct sockaddr_dl); break; default: nlog(LOG_ERR, "unknown af (%d) while parsing rtm", family); break; } } static void * getaddr(int addrid, int mask, void *addresses) { int i; void *p = addresses; if ((mask & addrid) == 0) return (NULL); for (i = 1; i < addrid; i <<= 1) { if (i & mask) nextaddr(&p); } return (p); } static void setaddr(int addrid, int *maskp, void *addressesp, struct sockaddr *address) { struct sockaddr *p = *((struct sockaddr **)addressesp); *maskp |= addrid; switch (address->sa_family) { case AF_INET: (void) memcpy(p, address, sizeof (struct sockaddr_in)); break; case AF_INET6: (void) memcpy(p, address, sizeof (struct sockaddr_in6)); break; case AF_LINK: (void) memcpy(p, address, sizeof (struct sockaddr_dl)); break; default: nlog(LOG_ERR, "setaddr: unknown af (%d) while setting addr", address->sa_family); break; } nextaddr(addressesp); } /* * 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. */ #include #include #include #include #include #include #include #include #include #include "events.h" #include "ncp.h" #include "ncu.h" #include "objects.h" #include "util.h" /* * sysevent_events.c - this file contains routines to retrieve sysevents * from the system and package them for high level processing. */ static sysevent_handle_t *sysevent_handle; /* * At present, we only handle EC_DEV_ADD/EC_DEV_REMOVE sysevents of * subclass ESC_NETWORK. These signify hotplug addition/removal. * For EC_DEV_ADD, we: * - extract the driver/instance sysevent attributes * - combine these to get interface name and create associated NCUs * at the link/IP level if required * - enable those instances * For EC_DEV_REMOVE, we: * - disable the associated link/IP NCUs */ static void sysevent_handler(sysevent_t *ev) { int32_t instance; char *driver; char if_name[LIFNAMSIZ]; boolean_t link_added; nvlist_t *attr_list; char *event_class = sysevent_get_class_name(ev); char *event_subclass = sysevent_get_subclass_name(ev); nwamd_event_t link_event = NULL; nlog(LOG_DEBUG, "sysevent_handler: event %s/%s", event_class, event_subclass); /* Make sure sysevent is of expected class/subclass */ if ((strcmp(event_class, EC_DEV_ADD) != 0 && strcmp(event_class, EC_DEV_REMOVE) != 0) || strcmp(event_subclass, ESC_NETWORK) != 0) { nlog(LOG_ERR, "sysevent_handler: unexpected sysevent " "class/subclass %s/%s", event_class, event_subclass); return; } link_added = (strcmp(event_class, EC_DEV_ADD) == 0); /* * Retrieve driver name and instance attributes, and combine to * get interface name. */ if (sysevent_get_attr_list(ev, &attr_list) != 0) { nlog(LOG_ERR, "sysevent_handler: sysevent_get_attr_list: %m"); return; } if (nvlist_lookup_string(attr_list, DEV_DRIVER_NAME, &driver) != 0 || nvlist_lookup_int32(attr_list, DEV_INSTANCE, &instance) != 0) { nlog(LOG_ERR, "sysevent_handler: nvlist_lookup " "of attributes failed: %m"); nvlist_free(attr_list); return; } (void) snprintf(if_name, LIFNAMSIZ, "%s%d", driver, instance); nvlist_free(attr_list); /* Ignore sysevent events for other zones */ if (!nwamd_link_belongs_to_this_zone(if_name)) return; /* Create event for link */ link_event = nwamd_event_init_link_action(if_name, link_added ? NWAM_ACTION_ADD : NWAM_ACTION_REMOVE); if (link_event != NULL) nwamd_event_enqueue(link_event); } /* ARGSUSED0 */ static void * sysevent_initialization(void *arg) { const char *subclass = ESC_NETWORK; do { nwamd_escalate(); sysevent_handle = sysevent_bind_handle(sysevent_handler); nwamd_deescalate(); (void) sleep(1); } while (sysevent_handle == NULL); /* * Subscribe to ESC_NETWORK subclass of EC_DEV_ADD and EC_DEV_REMOVE * events. As a result, we get sysevent notification of hotplug * add/remove events, which we handle above in sysevent_handler(). */ if (sysevent_subscribe_event(sysevent_handle, EC_DEV_ADD, &subclass, 1) != 0 || sysevent_subscribe_event(sysevent_handle, EC_DEV_REMOVE, &subclass, 1) != 0) pfail("sysevent_subscribe_event: %s", strerror(errno)); return (NULL); } /* * We can't initialize in the main thread because we may need to wait until * svc:/system/sysevent:default finishes starting up. So we create a thread to * initialize in. */ void nwamd_sysevent_events_init(void) { int rc; pthread_attr_t attr; rc = pthread_attr_init(&attr); if (rc != 0) { pfail("nwamd_sysevents_init: pthread_attr_init failed: %s", strerror(rc)); } rc = pthread_attr_setdetachstate(&attr, PTHREAD_CREATE_DETACHED); if (rc != 0) { pfail("nwamd_sysevents_init: pthread_attr_setdetachstate " "failed: %s", strerror(rc)); } rc = pthread_create(NULL, &attr, sysevent_initialization, NULL); if (rc != 0) { pfail("nwamd_sysevents_init: couldn't start sysevent init " "thread: %s", strerror(rc)); } (void) pthread_attr_destroy(&attr); } void nwamd_sysevent_events_fini(void) { if (sysevent_handle != NULL) { nwamd_escalate(); sysevent_unbind_handle(sysevent_handle); nwamd_deescalate(); } sysevent_handle = NULL; } /* * 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) 2010, Oracle and/or its affiliates. All rights reserved. */ /* * util.c contains a set of miscellaneous utility functions which, * among other things: * - start a child process * - look up the zone name * - look up/set SMF properties * - drop/escalate privs */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "util.h" #include "llp.h" extern char **environ; extern sigset_t original_sigmask; /* * A holder for all the resources needed to get a property value * using libscf. */ typedef struct scf_resources { scf_handle_t *sr_handle; scf_instance_t *sr_inst; scf_snapshot_t *sr_snap; scf_propertygroup_t *sr_pg; scf_property_t *sr_prop; scf_value_t *sr_val; scf_transaction_t *sr_tx; scf_transaction_entry_t *sr_ent; } scf_resources_t; static pthread_mutex_t uid_mutex = PTHREAD_MUTEX_INITIALIZER; static uid_t uid; static int uid_cnt; void nwamd_escalate(void) { priv_set_t *priv_set; priv_set = priv_str_to_set("zone", ",", NULL); if (priv_set == NULL) pfail("creating privilege set: %s", strerror(errno)); (void) pthread_mutex_lock(&uid_mutex); if (uid == 0) uid = getuid(); if (uid_cnt++ == 0) { if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { priv_freeset(priv_set); pfail("setppriv effective: %s", strerror(errno)); } } (void) pthread_mutex_unlock(&uid_mutex); priv_freeset(priv_set); } void nwamd_deescalate(void) { (void) pthread_mutex_lock(&uid_mutex); assert(uid_cnt > 0); if (--uid_cnt == 0) { priv_set_t *priv_set, *allpriv_set; /* build up our minimal set of privs. */ priv_set = priv_str_to_set("basic", ",", NULL); allpriv_set = priv_str_to_set("zone", ",", NULL); if (priv_set == NULL || allpriv_set == NULL) pfail("converting privilege sets: %s", strerror(errno)); (void) priv_addset(priv_set, PRIV_FILE_CHOWN_SELF); (void) priv_addset(priv_set, PRIV_FILE_DAC_READ); (void) priv_addset(priv_set, PRIV_FILE_DAC_WRITE); (void) priv_addset(priv_set, PRIV_NET_RAWACCESS); (void) priv_addset(priv_set, PRIV_NET_PRIVADDR); (void) priv_addset(priv_set, PRIV_PROC_AUDIT); (void) priv_addset(priv_set, PRIV_PROC_OWNER); (void) priv_addset(priv_set, PRIV_PROC_SETID); (void) priv_addset(priv_set, PRIV_SYS_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_IP_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_IPC_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_MOUNT); (void) priv_addset(priv_set, PRIV_SYS_NET_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_RES_CONFIG); (void) priv_addset(priv_set, PRIV_SYS_RESOURCE); /* * Since our zone might not have all these privs, * just ask for those that are available. */ priv_intersect(allpriv_set, priv_set); if (setppriv(PRIV_SET, PRIV_INHERITABLE, priv_set) == -1) { priv_freeset(allpriv_set); priv_freeset(priv_set); pfail("setppriv inheritable: %s", strerror(errno)); } /* * Need to ensure permitted set contains all privs so we can * escalate later. */ if (setppriv(PRIV_SET, PRIV_PERMITTED, allpriv_set) == -1) { priv_freeset(allpriv_set); priv_freeset(priv_set); pfail("setppriv permitted: %s", strerror(errno)); } /* * We need to find a smaller set of privs that are important to * us. Otherwise we really are not gaining much by doing this. */ if (setppriv(PRIV_SET, PRIV_EFFECTIVE, priv_set) == -1) { priv_freeset(allpriv_set); priv_freeset(priv_set); pfail("setppriv effective: %s", strerror(errno)); } priv_freeset(priv_set); priv_freeset(allpriv_set); } (void) pthread_mutex_unlock(&uid_mutex); } /* * * This starts a child process determined by command. If command contains a * slash then it is assumed to be a full path; otherwise the path is searched * for an executable file with the name command. Command is also used as * argv[0] of the new process. The rest of the arguments of the function * up to the first NULL make up pointers to arguments of the new process. * * This function returns child exit status on success and -1 on failure. * * NOTE: original_sigmask must be set before this function is called. */ int nwamd_start_childv(const char *command, char const * const *argv) { posix_spawnattr_t attr; sigset_t fullset; int i, rc, status, n; pid_t pid; char vbuf[1024]; vbuf[0] = 0; n = sizeof (vbuf); for (i = 1; argv[i] != NULL && n > 2; i++) { n -= strlcat(vbuf, " ", n); n -= strlcat(vbuf, argv[i], n); } if (argv[i] != NULL || n < 0) nlog(LOG_ERR, "nwamd_start_childv can't log full arg vector"); if ((rc = posix_spawnattr_init(&attr)) != 0) { nlog(LOG_DEBUG, "posix_spawnattr_init %d %s\n", rc, strerror(rc)); return (-1); } (void) sigfillset(&fullset); if ((rc = posix_spawnattr_setsigdefault(&attr, &fullset)) != 0) { nlog(LOG_DEBUG, "setsigdefault %d %s\n", rc, strerror(rc)); return (-1); } if ((rc = posix_spawnattr_setsigmask(&attr, &original_sigmask)) != 0) { nlog(LOG_DEBUG, "setsigmask %d %s\n", rc, strerror(rc)); return (-1); } if ((rc = posix_spawnattr_setflags(&attr, POSIX_SPAWN_SETSIGDEF|POSIX_SPAWN_SETSIGMASK)) != 0) { nlog(LOG_DEBUG, "setflags %d %s\n", rc, strerror(rc)); return (-1); } if ((rc = posix_spawnp(&pid, command, NULL, &attr, (char * const *)argv, environ)) > 0) { nlog(LOG_DEBUG, "posix_spawnp failed errno %d", rc); return (-1); } if ((rc = posix_spawnattr_destroy(&attr)) != 0) { nlog(LOG_DEBUG, "posix_spawn_attr_destroy %d %s\n", rc, strerror(rc)); return (-1); } (void) waitpid(pid, &status, 0); if (WIFSIGNALED(status) || WIFSTOPPED(status)) { i = WIFSIGNALED(status) ? WTERMSIG(status) : WSTOPSIG(status); nlog(LOG_ERR, "'%s%s' %s with signal %d (%s)", command, vbuf, (WIFSIGNALED(status) ? "terminated" : "stopped"), i, strsignal(i)); return (-2); } else { nlog(LOG_INFO, "'%s%s' completed normally: %d", command, vbuf, WEXITSTATUS(status)); return (WEXITSTATUS(status)); } } /* * For global zone, check if the link is used by a non-global * zone, note that the non-global zones doesn't need this check, * because zoneadm has taken care of this when the zone boots. * In the global zone, we ignore events for local-zone-owned links * since these are taken care of by the local zone's network * configuration services. */ boolean_t nwamd_link_belongs_to_this_zone(const char *linkname) { zoneid_t zoneid; char zonename[ZONENAME_MAX]; int ret; zoneid = getzoneid(); if (zoneid == GLOBAL_ZONEID) { datalink_id_t linkid; dladm_status_t status; char errstr[DLADM_STRSIZE]; if ((status = dladm_name2info(dld_handle, linkname, &linkid, NULL, NULL, NULL)) != DLADM_STATUS_OK) { nlog(LOG_DEBUG, "nwamd_link_belongs_to_this_zone: " "could not get linkid for %s: %s", linkname, dladm_status2str(status, errstr)); return (B_FALSE); } zoneid = ALL_ZONES; ret = zone_check_datalink(&zoneid, linkid); if (ret == 0) { (void) getzonenamebyid(zoneid, zonename, ZONENAME_MAX); nlog(LOG_DEBUG, "nwamd_link_belongs_to_this_zone: " "%s is used by non-global zone: %s", linkname, zonename); return (B_FALSE); } } return (B_TRUE); } /* * Inputs: * res is a pointer to the scf_resources_t to be released. */ static void release_scf_resources(scf_resources_t *res) { scf_entry_destroy(res->sr_ent); scf_transaction_destroy(res->sr_tx); scf_value_destroy(res->sr_val); scf_property_destroy(res->sr_prop); scf_pg_destroy(res->sr_pg); scf_snapshot_destroy(res->sr_snap); scf_instance_destroy(res->sr_inst); (void) scf_handle_unbind(res->sr_handle); scf_handle_destroy(res->sr_handle); } /* * Inputs: * fmri is the instance to look up * Outputs: * res is a pointer to an scf_resources_t. This is an internal * structure that holds all the handles needed to get a specific * property from the running snapshot; on a successful return it * contains the scf_value_t that should be passed to the desired * scf_value_get_foo() function, and must be freed after use by * calling release_scf_resources(). On a failure return, any * resources that may have been assigned to res are released, so * the caller does not need to do any cleanup in the failure case. * Returns: * 0 on success * -1 on failure */ static int create_scf_resources(const char *fmri, scf_resources_t *res) { res->sr_tx = NULL; res->sr_ent = NULL; res->sr_inst = NULL; res->sr_snap = NULL; res->sr_pg = NULL; res->sr_prop = NULL; res->sr_val = NULL; if ((res->sr_handle = scf_handle_create(SCF_VERSION)) == NULL) { return (-1); } if (scf_handle_bind(res->sr_handle) != 0) { scf_handle_destroy(res->sr_handle); return (-1); } if ((res->sr_inst = scf_instance_create(res->sr_handle)) == NULL) { goto failure; } if (scf_handle_decode_fmri(res->sr_handle, fmri, NULL, NULL, res->sr_inst, NULL, NULL, SCF_DECODE_FMRI_REQUIRE_INSTANCE) != 0) { goto failure; } if ((res->sr_snap = scf_snapshot_create(res->sr_handle)) == NULL) { goto failure; } if (scf_instance_get_snapshot(res->sr_inst, "running", res->sr_snap) != 0) { goto failure; } if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_tx = scf_transaction_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_ent = scf_entry_create(res->sr_handle)) == NULL) { goto failure; } return (0); failure: nlog(LOG_ERR, "create_scf_resources failed: %s", scf_strerror(scf_error())); release_scf_resources(res); return (-1); } /* * Inputs: * fmri is the instance to look up * pg is the property group to look up * prop is the property within that group to look up * running specifies if running snapshot is to be used * Outputs: * res is a pointer to an scf_resources_t. This is an internal * structure that holds all the handles needed to get a specific * property from the running snapshot; on a successful return it * contains the scf_value_t that should be passed to the desired * scf_value_get_foo() function, and must be freed after use by * calling release_scf_resources(). On a failure return, any * resources that may have been assigned to res are released, so * the caller does not need to do any cleanup in the failure case. * Returns: * 0 on success * -1 on failure */ static int get_property_value(const char *fmri, const char *pg, const char *prop, boolean_t running, scf_resources_t *res) { if (create_scf_resources(fmri, res) != 0) return (-1); if (scf_instance_get_pg_composed(res->sr_inst, running ? res->sr_snap : NULL, pg, res->sr_pg) != 0) { goto failure; } if (scf_pg_get_property(res->sr_pg, prop, res->sr_prop) != 0) { goto failure; } if (scf_property_get_value(res->sr_prop, res->sr_val) != 0) { goto failure; } return (0); failure: release_scf_resources(res); return (-1); } /* * Inputs: * lfmri is the instance fmri to look up * lpg is the property group to look up * lprop is the property within that group to look up * Outputs: * answer is a pointer to the property value * Returns: * 0 on success * -1 on failure * If successful, the property value is retured in *answer. * Otherwise, *answer is undefined, and it is up to the caller to decide * how to handle that case. */ int nwamd_lookup_boolean_property(const char *lfmri, const char *lpg, const char *lprop, boolean_t *answer) { int result = -1; scf_resources_t res; uint8_t prop_val; if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { /* * an error was already logged by get_property_value, * and it released any resources assigned to res before * returning. */ return (result); } if (scf_value_get_boolean(res.sr_val, &prop_val) != 0) { goto cleanup; } *answer = (boolean_t)prop_val; result = 0; cleanup: release_scf_resources(&res); return (result); } /* * Inputs: * lfmri is the instance fmri to look up * lpg is the property group to look up * lprop is the property within that group to look up * buf is the place to put the answer * bufsz is the size of buf * Outputs: * * Returns: * 0 on success * -1 on failure * If successful, the property value is retured in buf. * Otherwise, buf is undefined, and it is up to the caller to decide * how to handle that case. */ int nwamd_lookup_string_property(const char *lfmri, const char *lpg, const char *lprop, char *buf, size_t bufsz) { int result = -1; scf_resources_t res; if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { /* * The above function fails when trying to get a * non-persistent property group from the running snapshot. * Try going for the non-running snapshot. */ if (get_property_value(lfmri, lpg, lprop, B_FALSE, &res) != 0) { /* * an error was already logged by get_property_value, * and it released any resources assigned to res before * returning. */ return (result); } } if (scf_value_get_astring(res.sr_val, buf, bufsz) == 0) goto cleanup; result = 0; cleanup: release_scf_resources(&res); return (result); } /* * Inputs: * lfmri is the instance fmri to look up * lpg is the property group to look up * lprop is the property within that group to look up * Outputs: * answer is a pointer to the property value * Returns: * 0 on success * -1 on failure * If successful, the property value is retured in *answer. * Otherwise, *answer is undefined, and it is up to the caller to decide * how to handle that case. */ int nwamd_lookup_count_property(const char *lfmri, const char *lpg, const char *lprop, uint64_t *answer) { int result = -1; scf_resources_t res; if (get_property_value(lfmri, lpg, lprop, B_TRUE, &res) != 0) { /* * an error was already logged by get_property_value, * and it released any resources assigned to res before * returning. */ return (result); } if (scf_value_get_count(res.sr_val, answer) != 0) { goto cleanup; } result = 0; cleanup: release_scf_resources(&res); return (result); } static int set_property_value(scf_resources_t *res, const char *propname, scf_type_t proptype) { int result = -1; boolean_t new; retry: new = (scf_pg_get_property(res->sr_pg, propname, res->sr_prop) != 0); if (scf_transaction_start(res->sr_tx, res->sr_pg) == -1) { goto failure; } if (new) { if (scf_transaction_property_new(res->sr_tx, res->sr_ent, propname, proptype) == -1) { goto failure; } } else { if (scf_transaction_property_change(res->sr_tx, res->sr_ent, propname, proptype) == -1) { goto failure; } } if (scf_entry_add_value(res->sr_ent, res->sr_val) != 0) { goto failure; } result = scf_transaction_commit(res->sr_tx); if (result == 0) { scf_transaction_reset(res->sr_tx); if (scf_pg_update(res->sr_pg) == -1) { goto failure; } nlog(LOG_INFO, "set_property_value: transaction commit failed " "for %s; retrying", propname); goto retry; } if (result == -1) goto failure; return (0); failure: return (-1); } int nwamd_set_count_property(const char *fmri, const char *pg, const char *prop, uint64_t count) { scf_resources_t res; if (create_scf_resources(fmri, &res) != 0) return (-1); if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { if (scf_error() != SCF_ERROR_EXISTS) goto failure; if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, res.sr_pg) != 0) goto failure; } scf_value_set_count(res.sr_val, (uint64_t)count); if (set_property_value(&res, prop, SCF_TYPE_COUNT) != 0) goto failure; release_scf_resources(&res); return (0); failure: nlog(LOG_INFO, "nwamd_set_count_property: scf failure %s while " "setting %s", scf_strerror(scf_error()), prop); release_scf_resources(&res); return (-1); } int nwamd_set_string_property(const char *fmri, const char *pg, const char *prop, const char *str) { scf_resources_t res; if (create_scf_resources(fmri, &res) != 0) return (-1); if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { if (scf_error() != SCF_ERROR_EXISTS) goto failure; if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, res.sr_pg) != 0) goto failure; } if (scf_value_set_astring(res.sr_val, str) != 0) goto failure; if (set_property_value(&res, prop, SCF_TYPE_ASTRING) != 0) goto failure; release_scf_resources(&res); return (0); failure: nlog(LOG_INFO, "nwamd_set_string_property: scf failure %s while " "setting %s", scf_strerror(scf_error()), prop); release_scf_resources(&res); return (-1); } /* * Deletes property prop from property group pg in SMF instance fmri. * Returns 0 on success, -1 on failure. */ int nwamd_delete_scf_property(const char *fmri, const char *pg, const char *prop) { scf_resources_t res; int result = -1; if (create_scf_resources(fmri, &res) != 0) return (-1); if (scf_instance_add_pg(res.sr_inst, pg, SCF_GROUP_APPLICATION, SCF_PG_FLAG_NONPERSISTENT, res.sr_pg) != 0) { if (scf_error() != SCF_ERROR_EXISTS) goto failure; if (scf_instance_get_pg_composed(res.sr_inst, NULL, pg, res.sr_pg) != 0) goto failure; } if (scf_pg_get_property(res.sr_pg, prop, res.sr_prop) != 0) goto failure; retry: if (scf_transaction_start(res.sr_tx, res.sr_pg) == -1) goto failure; if (scf_transaction_property_delete(res.sr_tx, res.sr_ent, prop) == -1) goto failure; result = scf_transaction_commit(res.sr_tx); if (result == 0) { scf_transaction_reset(res.sr_tx); if (scf_pg_update(res.sr_pg) == -1) goto failure; goto retry; } if (result == -1) goto failure; release_scf_resources(&res); return (0); failure: release_scf_resources(&res); return (-1); } /* * 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 _UTIL_H #define _UTIL_H #include #include #include #include #include #include #include #include #include #include "events.h" #include "llp.h" #include "ncu.h" /* * A few functions here from files other than util.c, saves having * .h files for one or two functions. */ #define OUR_FMRI NWAM_FMRI #define OUR_PG NWAM_PG #define OUR_DEBUG_PROP_NAME "debug" #define OUR_AUTOCONF_PROP_NAME "autoconf" #define OUR_STRICT_BSSID_PROP_NAME "strict_bssid" #define OUR_ACTIVE_NCP_PROP_NAME NWAM_PROP_ACTIVE_NCP #define OUR_CONDITION_CHECK_INTERVAL_PROP_NAME "condition_check_interval" #define OUR_WIRELESS_SCAN_INTERVAL_PROP_NAME "scan_interval" #define OUR_WIRELESS_SCAN_LEVEL_PROP_NAME "scan_level" #define OUR_NCU_WAIT_TIME_PROP_NAME "ncu_wait_time" #define OUR_VERSION_PROP_NAME "version" #define NET_LOC_FMRI "svc:/network/location:default" #define NET_LOC_PG "location" #define NET_LOC_SELECTED_PROP "selected" #define NSEC_TO_SEC(nsec) (nsec) / (long)NANOSEC #define NSEC_TO_FRACNSEC(nsec) (nsec) % (long)NANOSEC #define SEC_TO_NSEC(sec) (sec) * (long)NANOSEC extern boolean_t debug; extern boolean_t shutting_down; /* logging.c: log support functions */ extern void nlog(int, const char *, ...); extern void pfail(const char *fmt, ...); extern int syslog_stack(uintptr_t addr, int sig, void *arg); /* door_if.c: door interface functions */ extern void nwamd_door_init(void); extern void nwamd_door_fini(void); /* util.c: utility & ipc functions */ extern int nwamd_start_childv(const char *, const char * const *); extern boolean_t nwamd_link_belongs_to_this_zone(const char *); extern void nwamd_escalate(void); extern void nwamd_deescalate(void); extern void nwamd_drop_unneeded_privs(void); extern void nwamd_escalate_privs(void); /* SCF helper functions */ extern int nwamd_lookup_boolean_property(const char *, const char *, const char *, boolean_t *); extern int nwamd_lookup_count_property(const char *, const char *, const char *, uint64_t *); extern int nwamd_lookup_string_property(const char *, const char *, const char *, char *, size_t); extern int nwamd_set_count_property(const char *, const char *, const char *, uint64_t); extern int nwamd_set_string_property(const char *, const char *, const char *, const char *); extern int nwamd_delete_scf_property(const char *, const char *, const char *); #endif /* _UTIL_H */