/* * 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. */ /* * 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" #include #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; /* * 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) || strcmp(proto, db_proto) != 0) { /* no intersection - different protocols. */ return (B_FALSE); } if ((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) || 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_getaddr_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; 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; err = pthread_create(&tid, NULL, ipmgmt_db_restore_thread, NULL); 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]; 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; } *errp = 0; /* we updated the DB entry, so do not continue */ return (B_FALSE); } /* * For the given `cbarg->cb_ifname' interface, retrieves any persistent * interface information (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_getif_cbarg_t *cbarg = arg; char *ifname = cbarg->cb_ifname; char *intf = NULL; ipadm_if_info_t *ifp = NULL; sa_family_t af; char *afstr; *errp = 0; if (nvlist_lookup_string(db_nvl, IPADM_NVP_FAMILY, &afstr) != 0 || nvlist_lookup_string(db_nvl, IPADM_NVP_IFNAME, &intf) != 0 || (ifname[0] != '\0' && strcmp(ifname, intf) != 0)) { return (B_TRUE); } af = atoi(afstr); for (ifp = cbarg->cb_ifinfo; ifp != NULL; ifp = ifp->ifi_next) { if (strcmp(ifp->ifi_name, intf) == 0) break; } if (ifp == NULL) { ipadm_if_info_t *new; if ((new = calloc(1, sizeof (*new))) == NULL) { *errp = ENOMEM; return (B_FALSE); /* don't continue the walk */ } new->ifi_next = cbarg->cb_ifinfo; cbarg->cb_ifinfo = new; ifp = new; (void) strlcpy(ifp->ifi_name, intf, sizeof (ifp->ifi_name)); } if (af == AF_INET) { ifp->ifi_pflags |= IFIF_IPV4; } else { assert(af == AF_INET6); ifp->ifi_pflags |= IFIF_IPV6; } /* Terminate the walk if we found both v4 and v6 interfaces. */ if (ifname[0] != '\0' && (ifp->ifi_pflags & IFIF_IPV4) && (ifp->ifi_pflags & IFIF_IPV6)) 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 *afstr; 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_lookup_string(db_nvl, IPADM_NVP_FAMILY, &afstr) == 0) { if (atoi(afstr) == cbarg->cb_family) goto delete; 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: /* this should never be the case, today */ assert(0); 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; (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->am_linklocal == nodep->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; if (head->am_atype == IPADM_ADDR_IPV6_ADDRCONF) { head->am_ifid = nodep->am_ifid; head->am_linklocal = nodep->am_linklocal; } } 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; if (np->am_atype == IPADM_ADDR_IPV6_ADDRCONF) { struct sockaddr_in6 *in6; in6 = (struct sockaddr_in6 *)&np->am_ifid; if (np->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; } } else { if ((err = nvlist_add_string(*nvl, IPADM_NVP_IPNUMADDR, "")) != 0) goto fail; } 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 = (struct sockaddr_in6 *)&node.am_ifid; if (strcmp(strval, "default") == 0) { bzero(in6, sizeof (node.am_ifid)); node.am_linklocal = B_TRUE; } else { (void) inet_pton(AF_INET6, strval, &in6->sin6_addr); if (IN6_IS_ADDR_UNSPECIFIED( &in6->sin6_addr)) node.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); } 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; /* * Inputs: * res is a pointer to the scf_resources_t to be released. */ static 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); } /* * Inputs: * fmri is the instance to look up * Outputs: * res is a pointer to an scf_resources_t. This is an internal * structure that holds all the handles needed to get a specific * property from the running snapshot; on a successful return it * contains the scf_value_t that should be passed to the desired * scf_value_get_foo() function, and must be freed after use by * calling release_scf_resources(). On a failure return, any * resources that may have been assigned to res are released, so * the caller does not need to do any cleanup in the failure case. * Returns: * 0 on success * -1 on failure */ static int 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; } if ((res->sr_pg = scf_pg_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_prop = scf_property_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_val = scf_value_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_tx = scf_transaction_create(res->sr_handle)) == NULL) { goto failure; } if ((res->sr_ent = scf_entry_create(res->sr_handle)) == NULL) { goto failure; } return (0); failure: ipmgmt_release_scf_resources(res); return (-1); } static int ipmgmt_set_property_value(scf_resources_t *res, const char *propname, scf_type_t proptype) { int result = -1; boolean_t new; retry: new = (scf_pg_get_property(res->sr_pg, propname, res->sr_prop) != 0); if (scf_transaction_start(res->sr_tx, res->sr_pg) == -1) { goto failure; } if (new) { if (scf_transaction_property_new(res->sr_tx, res->sr_ent, propname, proptype) == -1) { goto failure; } } else { if (scf_transaction_property_change(res->sr_tx, res->sr_ent, propname, proptype) == -1) { goto failure; } } if (scf_entry_add_value(res->sr_ent, res->sr_val) != 0) { goto failure; } result = scf_transaction_commit(res->sr_tx); if (result == 0) { scf_transaction_reset(res->sr_tx); if (scf_pg_update(res->sr_pg) == -1) { goto failure; } goto retry; } if (result == -1) goto failure; return (0); failure: return (-1); } /* * Returns TRUE if this is the first boot, else return FALSE. The * "ipmgmtd/first_boot_done" property is persistently set up on * IPMGMTD_FMRI on the first boot. Note that the presence of * "first_boot_done" itself is sufficient to indicate that this is * not the first boot i.e., the value of the property is immaterial. */ extern boolean_t ipmgmt_first_boot() { scf_simple_prop_t *prop; ssize_t numvals; scf_resources_t res; scf_error_t err; if (ipmgmt_create_scf_resources(IPMGMTD_FMRI, &res) != 0) return (B_TRUE); /* err on the side of caution */ prop = scf_simple_prop_get(res.sr_handle, IPMGMTD_FMRI, "ipmgmtd", "first_boot_done"); numvals = scf_simple_prop_numvalues(prop); if (numvals > 0) { scf_simple_prop_free(prop); ipmgmt_release_scf_resources(&res); return (B_FALSE); } /* * mark the first boot by setting ipmgmtd/first_boot_done to true */ if (scf_instance_add_pg(res.sr_inst, "ipmgmtd", SCF_GROUP_APPLICATION, 0, res.sr_pg) != 0) { if ((err = scf_error()) != SCF_ERROR_EXISTS) goto failure; /* * err == SCF_ERROR_EXISTS is by itself sufficient to declare * that this is not the first boot, but we create a simple * property as a place-holder, so that we don't leave an * empty process group behind. */ if (scf_instance_get_pg_composed(res.sr_inst, NULL, "ipmgmtd", res.sr_pg) != 0) { err = scf_error(); goto failure; } } if (scf_value_set_astring(res.sr_val, "true") != 0) { err = scf_error(); goto failure; } if (ipmgmt_set_property_value(&res, "first_boot_done", SCF_TYPE_ASTRING) != 0) { ipmgmt_log(LOG_WARNING, "Could not set rval of first_boot_done"); } failure: ipmgmt_log(LOG_WARNING, "ipmgmt_first_boot scf error %s", scf_strerror(err)); ipmgmt_release_scf_resources(&res); return (B_TRUE); }