# # 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