/* * 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. */ #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include #include "libipadm_impl.h" static ipadm_status_t i_ipadm_slifname_arp(char *, uint64_t, int); static ipadm_status_t i_ipadm_slifname(ipadm_handle_t, char *, char *, uint64_t, int, uint32_t); static ipadm_status_t i_ipadm_create_ipmp_peer(ipadm_handle_t, char *, sa_family_t); static ipadm_status_t i_ipadm_persist_if(ipadm_handle_t, const char *, sa_family_t, uint32_t); static ipadm_status_t i_ipadm_allocate_ifinfo(ipadm_if_info_t **); static ipadm_status_t i_ipadm_get_db_if(ipadm_handle_t, const char *, nvlist_t **); static ipadm_status_t i_ipadm_nvl2ifinfo(nvlist_t *, ipadm_if_info_t **); static ipadm_status_t i_ipadm_fill_cmembers(char *, ipadm_ipmp_members_t *); static ipadm_status_t i_ipadm_fill_pmembers(nvlist_t *, ipadm_ipmp_members_t *); static ipadm_status_t i_ipadm_add_persistent_if_info(ipadm_if_info_t *, ipadm_if_info_t *); static void i_ipadm_free_ipmp_members(ipadm_ipmp_members_t *); static ipadm_status_t i_ipadm_persist_update_ipmp(ipadm_handle_t, const char *, const char *, ipadm_ipmp_op_t); static ipadm_status_t i_ipadm_update_ipmp(ipadm_handle_t, const char *, const char *, uint32_t, ipadm_ipmp_op_t); /* * Returns B_FALSE if the interface in `ifname' has at least one address that is * IFF_UP in the addresses in `ifa'. */ static boolean_t i_ipadm_is_if_down(char *ifname, struct ifaddrs *ifa) { struct ifaddrs *ifap; char cifname[LIFNAMSIZ]; char *sep; for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) { (void) strlcpy(cifname, ifap->ifa_name, sizeof (cifname)); if ((sep = strrchr(cifname, IPADM_LOGICAL_SEP)) != NULL) *sep = '\0'; /* * If this condition is true, there is at least one * address that is IFF_UP. So, we need to return B_FALSE. */ if (strcmp(cifname, ifname) == 0 && (ifap->ifa_flags & IFF_UP)) { return (B_FALSE); } } /* We did not find any IFF_UP addresses. */ return (B_TRUE); } /* * Retrieves the information for the interface `ifname' from active * config if `ifname' is specified and returns the result in the list `if_info'. * Otherwise, it retrieves the information for all the interfaces in * the active config and returns the result in the list `if_info'. */ static ipadm_status_t i_ipadm_active_if_info(ipadm_handle_t iph, const char *ifname, ipadm_if_info_t **if_info, int64_t lifc_flags) { struct lifreq *buf; struct lifreq *lifrp; struct lifreq lifrl; ipadm_if_info_t *last = NULL; ipadm_if_info_t *ifp; int s; int n; int numifs; ipadm_status_t status = IPADM_SUCCESS; *if_info = NULL; /* * Get information for all interfaces. */ if (getallifs(iph->iph_sock, 0, &buf, &numifs, lifc_flags) != 0) return (ipadm_errno2status(errno)); lifrp = buf; for (n = 0; n < numifs; n++, lifrp++) { /* Skip interfaces with logical num != 0 */ if (i_ipadm_get_lnum(lifrp->lifr_name) != 0) continue; /* * Skip the current interface if a specific `ifname' has * been requested and current interface does not match * `ifname'. */ if (ifname != NULL && strcmp(lifrp->lifr_name, ifname) != 0) continue; /* * Check if the interface already exists in our list. * If it already exists, we need to update its flags. */ for (ifp = *if_info; ifp != NULL; ifp = ifp->ifi_next) { if (strcmp(lifrp->lifr_name, ifp->ifi_name) == 0) break; } if (ifp == NULL) { if ((status = i_ipadm_allocate_ifinfo(&ifp)) != IPADM_SUCCESS) break; (void) strlcpy(ifp->ifi_name, lifrp->lifr_name, sizeof (ifp->ifi_name)); /* Update the `ifi_next' pointer for this new node */ if (*if_info == NULL) *if_info = ifp; else last->ifi_next = ifp; last = ifp; } /* * Retrieve the flags for the interface by doing a * SIOCGLIFFLAGS to populate the `ifi_cflags' field. */ (void) strlcpy(lifrl.lifr_name, lifrp->lifr_name, sizeof (lifrl.lifr_name)); s = (lifrp->lifr_addr.ss_family == AF_INET) ? iph->iph_sock : iph->iph_sock6; if (ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0) continue; /* a regular interface by default */ ifp->ifi_class = IPADM_IF_CLASS_REGULAR; if (lifrl.lifr_flags & IFF_BROADCAST) ifp->ifi_cflags |= IFIF_BROADCAST; if (lifrl.lifr_flags & IFF_MULTICAST) ifp->ifi_cflags |= IFIF_MULTICAST; if (lifrl.lifr_flags & IFF_POINTOPOINT) ifp->ifi_cflags |= IFIF_POINTOPOINT; if (lifrl.lifr_flags & IFF_VIRTUAL) { ifp->ifi_cflags |= IFIF_VIRTUAL; ifp->ifi_class = IPADM_IF_CLASS_VIRTUAL; } if (lifrl.lifr_flags & IFF_IPMP) { ifp->ifi_cflags |= IFIF_IPMP; ifp->ifi_class = IPADM_IF_CLASS_IPMP; } if (lifrl.lifr_flags & IFF_STANDBY) ifp->ifi_cflags |= IFIF_STANDBY; if (lifrl.lifr_flags & IFF_INACTIVE) ifp->ifi_cflags |= IFIF_INACTIVE; if (lifrl.lifr_flags & IFF_VRRP) ifp->ifi_cflags |= IFIF_VRRP; if (lifrl.lifr_flags & IFF_NOACCEPT) ifp->ifi_cflags |= IFIF_NOACCEPT; if (lifrl.lifr_flags & IFF_IPV4) ifp->ifi_cflags |= IFIF_IPV4; if (lifrl.lifr_flags & IFF_IPV6) ifp->ifi_cflags |= IFIF_IPV6; if (lifrl.lifr_flags & IFF_L3PROTECT) ifp->ifi_cflags |= IFIF_L3PROTECT; /* * Retrieve active IPMP members. This may fail in in.mpathd if * the IPMP interface has just been created with no members. * Hence, ignore errors, cmembers will just be empty. */ if (ifp->ifi_class == IPADM_IF_CLASS_IPMP) { if (ioctl(s, SIOCGLIFGROUPNAME, (caddr_t)&lifrl) == 0) { (void) i_ipadm_fill_cmembers( lifrl.lifr_groupname, &ifp->ifi_ipmp_cmembers); } } } free(buf); if (status != IPADM_SUCCESS) { ipadm_free_if_info(*if_info); *if_info = NULL; } return (status); } /* * Returns the interface information for `ifname' in `if_info' from persistent * config if `ifname' is non-null. Otherwise, it returns all the interfaces * from persistent config in `if_info'. */ static ipadm_status_t i_ipadm_persist_if_info(ipadm_handle_t iph, const char *ifname, ipadm_if_info_t **if_info) { ipadm_status_t status = IPADM_SUCCESS; nvlist_t *ifs_info_nvl; *if_info = NULL; if ((status = i_ipadm_get_db_if(iph, ifname, &ifs_info_nvl)) != IPADM_SUCCESS) return (status); assert(ifs_info_nvl != NULL); return (i_ipadm_nvl2ifinfo(ifs_info_nvl, if_info)); } static ipadm_status_t i_ipadm_nvl2ifinfo(nvlist_t *ifs_info_nvl, ipadm_if_info_t **if_info) { ipadm_if_info_t *ific = NULL, *ifil = NULL; nvlist_t *if_info_nvl; nvpair_t *nvp; char *strval; ipadm_status_t status = IPADM_SUCCESS; uint16_t *families; uint_t nelem = 0; for (nvp = nvlist_next_nvpair(ifs_info_nvl, NULL); nvp != NULL; nvp = nvlist_next_nvpair(ifs_info_nvl, nvp)) { if (nvpair_value_nvlist(nvp, &if_info_nvl) != 0) continue; status = i_ipadm_allocate_ifinfo(&ific); if (status != IPADM_SUCCESS) { ipadm_free_if_info(*if_info); break; } if (nvlist_lookup_string(if_info_nvl, IPADM_NVP_IFNAME, &strval) != 0) { ipadm_free_if_info(ific); ific = NULL; continue; } (void) strlcpy(ific->ifi_name, strval, sizeof (ific->ifi_name)); if (nvlist_lookup_uint16_array(if_info_nvl, IPADM_NVP_FAMILIES, &families, &nelem) == 0) { while (nelem-- > 0) { if (families[nelem] == AF_INET) ific->ifi_pflags |= IFIF_IPV4; else if (families[nelem] == AF_INET6) ific->ifi_pflags |= IFIF_IPV6; } } if (nvlist_lookup_string(if_info_nvl, IPADM_NVP_IFCLASS, &strval) == 0) ific->ifi_class = atoi(strval); else ific->ifi_class = IPADM_IF_CLASS_REGULAR; if (ific->ifi_class == IPADM_IF_CLASS_IPMP) /* do not expect any failures there */ (void) i_ipadm_fill_pmembers(if_info_nvl, &ific->ifi_ipmp_pmembers); if (*if_info == NULL) *if_info = ific; else ifil->ifi_next = ific; ifil = ific; } nvlist_free(ifs_info_nvl); return (status); } /* * Fill the ipadm_if_info_t->ifi_ipmp_pmembers by info from * ipadm DB */ static ipadm_status_t i_ipadm_fill_pmembers(nvlist_t *if_info_nvl, ipadm_ipmp_members_t *pmembers) { uint_t nelem = 0; char **members; ipadm_ipmp_member_t *ipmp_member; if (nvlist_lookup_string_array(if_info_nvl, IPADM_NVP_MIFNAMES, &members, &nelem) != 0) return (IPADM_SUCCESS); while (nelem-- > 0) { if ((ipmp_member = calloc(1, sizeof (ipadm_ipmp_member_t))) == NULL) return (ipadm_errno2status(errno)); (void) strlcpy(ipmp_member->if_name, members[nelem], sizeof (ipmp_member->if_name)); list_insert_tail(pmembers, ipmp_member); } return (IPADM_SUCCESS); } /* * Fill the ipadm_if_info_t->ifi_ipmp_cmembers by info from * kernel (libipmp is used to retrieve the required info) */ static ipadm_status_t i_ipadm_fill_cmembers(char *grname, ipadm_ipmp_members_t *cmembers) { ipmp_handle_t ipmp_handle; ipmp_groupinfo_t *grinfo; ipmp_iflist_t *iflistp; ipadm_ipmp_member_t *ipmp_member; ipadm_status_t ipadm_status = IPADM_SUCCESS; int i; if (ipmp_open(&ipmp_handle) != IPMP_SUCCESS) return (IPADM_FAILURE); if (ipmp_getgroupinfo(ipmp_handle, grname, &grinfo) != IPMP_SUCCESS) { ipadm_status = IPADM_FAILURE; goto fail2; } iflistp = grinfo->gr_iflistp; for (i = 0; i < iflistp->il_nif; i++) { if ((ipmp_member = calloc(1, sizeof (ipadm_ipmp_member_t))) == NULL) { ipadm_status = ipadm_errno2status(errno); goto fail1; } (void) strlcpy(ipmp_member->if_name, iflistp->il_ifs[i], sizeof (ipmp_member->if_name)); list_insert_tail(cmembers, ipmp_member); } fail1: ipmp_freegroupinfo(grinfo); fail2: ipmp_close(ipmp_handle); return (ipadm_status); } /* * Collects information for `ifname' if one is specified from both * active and persistent config in `if_info'. If no `ifname' is specified, * this returns all the interfaces in active and persistent config in * `if_info'. */ ipadm_status_t i_ipadm_get_all_if_info(ipadm_handle_t iph, const char *ifname, ipadm_if_info_t **if_info, int64_t lifc_flags) { ipadm_status_t status; ipadm_if_info_t *aifinfo = NULL; ipadm_if_info_t *pifinfo = NULL; ipadm_if_info_t *aifp; ipadm_if_info_t *pifp; ipadm_if_info_t *last = NULL; struct ifaddrs *ifa; struct ifaddrs *ifap; /* * Retrive the information for the requested `ifname' or all * interfaces from active configuration. */ retry: status = i_ipadm_active_if_info(iph, ifname, &aifinfo, lifc_flags); if (status != IPADM_SUCCESS) return (status); /* Get the interface state for each interface in `aifinfo'. */ if (aifinfo != NULL) { /* We need all addresses to get the interface state */ if (getallifaddrs(AF_UNSPEC, &ifa, (LIFC_NOXMIT|LIFC_TEMPORARY| LIFC_ALLZONES|LIFC_UNDER_IPMP)) != 0) { status = ipadm_errno2status(errno); goto fail; } for (aifp = aifinfo; aifp != NULL; aifp = aifp->ifi_next) { /* * Find the `ifaddrs' structure from `ifa' * for this interface. We need the IFF_* flags * to find the interface state. */ for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) { if (strcmp(ifap->ifa_name, aifp->ifi_name) == 0) break; } if (ifap == NULL) { /* * The interface might have been removed * from kernel. Retry getting all the active * interfaces. */ freeifaddrs(ifa); ipadm_free_if_info(aifinfo); aifinfo = NULL; goto retry; } if (!(ifap->ifa_flags & IFF_RUNNING) || (ifap->ifa_flags & IFF_FAILED)) aifp->ifi_state = IFIS_FAILED; else if (ifap->ifa_flags & IFF_OFFLINE) aifp->ifi_state = IFIS_OFFLINE; else if (i_ipadm_is_if_down(aifp->ifi_name, ifa)) aifp->ifi_state = IFIS_DOWN; else aifp->ifi_state = IFIS_OK; if (aifp->ifi_next == NULL) last = aifp; } freeifaddrs(ifa); } /* * Get the persistent interface information in `pifinfo'. */ status = i_ipadm_persist_if_info(iph, ifname, &pifinfo); if (status == IPADM_NOTFOUND) { *if_info = aifinfo; return (IPADM_SUCCESS); } if (status != IPADM_SUCCESS) goto fail; /* * Process the persistent interface information. * * First try to get the persistent "standby" property, as that isn't * retrieved by i_ipadm_persist_if_info(). * * Next, if a persistent interface is also found in `aifinfo', update * its entry in `aifinfo' with the persistent information from * `pifinfo'. If an interface is found in `pifinfo', but not in * `aifinfo', it means that this interface was disabled. We should * add this interface to `aifinfo' and set it state to IFIF_DISABLED. */ for (pifp = pifinfo; pifp != NULL; pifp = pifp->ifi_next) { char buf[10] = ""; uint_t bufsize = sizeof (buf); status = ipadm_get_ifprop(iph, pifp->ifi_name, "standby", buf, &bufsize, MOD_PROTO_IP, IPADM_OPT_PERSIST); if (status == IPADM_SUCCESS && strcmp(buf, "on") == 0) pifp->ifi_pflags |= IFIF_STANDBY; for (aifp = aifinfo; aifp != NULL; aifp = aifp->ifi_next) { if (strcmp(aifp->ifi_name, pifp->ifi_name) == 0) { break; } } if (aifp == NULL) { if ((status = i_ipadm_allocate_ifinfo(&aifp)) != IPADM_SUCCESS) goto fail; (void) strlcpy(aifp->ifi_name, pifp->ifi_name, sizeof (aifp->ifi_name)); aifp->ifi_next = NULL; aifp->ifi_state = IFIS_DISABLED; if (last != NULL) last->ifi_next = aifp; else aifinfo = aifp; last = aifp; } if ((status = i_ipadm_add_persistent_if_info(aifp, pifp)) != IPADM_SUCCESS) goto fail; } *if_info = aifinfo; ipadm_free_if_info(pifinfo); return (IPADM_SUCCESS); fail: *if_info = NULL; ipadm_free_if_info(aifinfo); ipadm_free_if_info(pifinfo); return (status); } /* * Updates active if_info by data from persistent if_info */ static ipadm_status_t i_ipadm_add_persistent_if_info(ipadm_if_info_t *aifp, ipadm_if_info_t *pifp) { ipadm_ipmp_member_t *pp_ipmp_member, *ap_ipmp_member; ipadm_ipmp_members_t *apmembers = &aifp->ifi_ipmp_pmembers; ipadm_ipmp_members_t *ppmembers = &pifp->ifi_ipmp_pmembers; aifp->ifi_pflags = pifp->ifi_pflags; aifp->ifi_class = pifp->ifi_class; for (pp_ipmp_member = list_head(ppmembers); pp_ipmp_member; pp_ipmp_member = list_next(ppmembers, pp_ipmp_member)) { if ((ap_ipmp_member = calloc(1, sizeof (ipadm_ipmp_member_t))) == NULL) return (ipadm_errno2status(errno)); (void) strlcpy(ap_ipmp_member->if_name, pp_ipmp_member->if_name, sizeof (ap_ipmp_member->if_name)); list_insert_tail(apmembers, ap_ipmp_member); } return (IPADM_SUCCESS); } static ipadm_status_t i_ipadm_allocate_ifinfo(ipadm_if_info_t **if_info) { *if_info = calloc(1, sizeof (ipadm_if_info_t)); if (*if_info == NULL) return (ipadm_errno2status(errno)); /* List of active (current) members */ list_create(&((*if_info)->ifi_ipmp_cmembers), sizeof (ipadm_ipmp_member_t), offsetof(ipadm_ipmp_member_t, node)); /* List of persistent members */ list_create(&((*if_info)->ifi_ipmp_pmembers), sizeof (ipadm_ipmp_member_t), offsetof(ipadm_ipmp_member_t, node)); return (IPADM_SUCCESS); } /* * Reads all the interface lines from the persistent DB into the nvlist `onvl', * when `ifname' is NULL. * If an `ifname' is specified, then the interface line corresponding to * that name will be returned. */ static ipadm_status_t i_ipadm_get_db_if(ipadm_handle_t iph, const char *ifname, nvlist_t **onvl) { ipmgmt_getif_arg_t garg; /* Populate the door_call argument structure */ bzero(&garg, sizeof (garg)); garg.ia_cmd = IPMGMT_CMD_GETIF; if (ifname != NULL) (void) strlcpy(garg.ia_ifname, ifname, sizeof (garg.ia_ifname)); return (i_ipadm_call_ipmgmtd(iph, (void *) &garg, sizeof (garg), onvl)); } int i_ipadm_get_lnum(const char *ifname) { char *num = strrchr(ifname, IPADM_LOGICAL_SEP); if (num == NULL) return (0); return (atoi(++num)); } /* * Sets the output argument `exists' to true or false based on whether * any persistent configuration is available for `ifname' and returns * IPADM_SUCCESS as status. If the persistent information cannot be retrieved, * `exists' is unmodified and an error status is returned. */ ipadm_status_t i_ipadm_if_pexists(ipadm_handle_t iph, const char *ifname, sa_family_t af, boolean_t *exists) { ipadm_if_info_t *ifinfo; ipadm_status_t status; /* * if IPH_IPMGMTD is set, we know that the caller (ipmgmtd) already * knows about persistent configuration in the first place, so we * just return success. */ if (iph->iph_flags & IPH_IPMGMTD) { *exists = B_FALSE; return (IPADM_SUCCESS); } status = i_ipadm_persist_if_info(iph, ifname, &ifinfo); if (status == IPADM_SUCCESS) { *exists = ((af == AF_INET && (ifinfo->ifi_pflags & IFIF_IPV4)) || (af == AF_INET6 && (ifinfo->ifi_pflags & IFIF_IPV6))); ipadm_free_if_info(ifinfo); } else if (status == IPADM_NOTFOUND) { status = IPADM_SUCCESS; *exists = B_FALSE; } return (status); } /* * Open "/dev/udp{,6}" for use as a multiplexor to PLINK the interface stream * under. We use "/dev/udp" instead of "/dev/ip" since STREAMS will not let * you PLINK a driver under itself, and "/dev/ip" is typically the driver at * the bottom of the stream for tunneling interfaces. */ ipadm_status_t ipadm_open_arp_on_udp(const char *udp_dev_name, int *fd) { int err; if ((*fd = open(udp_dev_name, O_RDWR)) == -1) return (ipadm_errno2status(errno)); /* * Pop off all undesired modules (note that the user may have * configured autopush to add modules above udp), and push the * arp module onto the resulting stream. This is used to make * IP+ARP be able to atomically track the muxid for the I_PLINKed * STREAMS, thus it isn't related to ARP running the ARP protocol. */ while (ioctl(*fd, I_POP, 0) != -1) ; if (errno == EINVAL && ioctl(*fd, I_PUSH, ARP_MOD_NAME) != -1) return (IPADM_SUCCESS); err = errno; (void) close(*fd); return (ipadm_errno2status(err)); } /* * i_ipadm_create_ipmp() is called from i_ipadm_create_ipmp_peer() when an * underlying interface in an ipmp group G is plumbed for an address family, * but the meta-interface for the other address family `af' does not exist * yet for the group G. If `af' is IPv6, we need to bring up the * link-local address. */ static ipadm_status_t i_ipadm_create_ipmp(ipadm_handle_t iph, char *ifname, sa_family_t af, const char *grname, uint32_t ipadm_flags) { ipadm_status_t status; struct lifreq lifr; int sock; int err; assert(ipadm_flags & IPADM_OPT_IPMP); /* Create the ipmp underlying interface */ status = i_ipadm_create_if(iph, ifname, af, ipadm_flags); if (status != IPADM_SUCCESS && status != IPADM_IF_EXISTS) return (status); /* * To preserve backward-compatibility, always bring up the link-local * address for implicitly-created IPv6 IPMP interfaces. */ if (af == AF_INET6) (void) i_ipadm_set_flags(iph, ifname, AF_INET6, IFF_UP, 0); sock = (af == AF_INET ? iph->iph_sock : iph->iph_sock6); /* * If the caller requested a different group name, issue a * SIOCSLIFGROUPNAME on the new IPMP interface. */ bzero(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (strcmp(lifr.lifr_name, grname) != 0) { (void) strlcpy(lifr.lifr_groupname, grname, LIFGRNAMSIZ); if (ioctl(sock, SIOCSLIFGROUPNAME, &lifr) == -1) { err = errno; /* Remove the interface we created. */ if (status == IPADM_SUCCESS) { (void) i_ipadm_delete_if(iph, ifname, af, ipadm_flags); } return (ipadm_errno2status(err)); } } return (IPADM_SUCCESS); } /* * Checks if `ifname' is plumbed and in an IPMP group on its "other" address * family. If so, create a matching IPMP group for address family `af'. */ static ipadm_status_t i_ipadm_create_ipmp_peer(ipadm_handle_t iph, char *ifname, sa_family_t af) { lifgroupinfo_t lifgr; ipadm_status_t status = IPADM_SUCCESS; struct lifreq lifr; int other_af_sock; assert(af == AF_INET || af == AF_INET6); other_af_sock = (af == AF_INET ? iph->iph_sock6 : iph->iph_sock); /* * iph is the handle for the interface that we are trying to plumb. * other_af_sock is the socket for the "other" address family. */ bzero(&lifr, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (ioctl(other_af_sock, SIOCGLIFGROUPNAME, &lifr) != 0) return (IPADM_SUCCESS); (void) strlcpy(lifgr.gi_grname, lifr.lifr_groupname, LIFGRNAMSIZ); if (ioctl(other_af_sock, SIOCGLIFGROUPINFO, &lifgr) != 0) return (IPADM_SUCCESS); /* * If `ifname' *is* the IPMP group interface, or if the relevant * address family is already configured, then there's nothing to do. */ if (strcmp(lifgr.gi_grifname, ifname) == 0 || (af == AF_INET && lifgr.gi_v4) || (af == AF_INET6 && lifgr.gi_v6)) { return (IPADM_SUCCESS); } status = i_ipadm_create_ipmp(iph, lifgr.gi_grifname, af, lifgr.gi_grname, IPADM_OPT_ACTIVE|IPADM_OPT_IPMP); return (status); } /* * Issues the ioctl SIOCSLIFNAME to kernel on the given ARP stream fd. */ static ipadm_status_t i_ipadm_slifname_arp(char *ifname, uint64_t flags, int fd) { struct lifreq lifr; ifspec_t ifsp; bzero(&lifr, sizeof (lifr)); (void) ifparse_ifspec(ifname, &ifsp); lifr.lifr_ppa = ifsp.ifsp_ppa; lifr.lifr_flags = flags; (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); /* * Tell ARP the name and unit number for this interface. * Note that arp has no support for transparent ioctls. */ if (i_ipadm_strioctl(fd, SIOCSLIFNAME, (char *)&lifr, sizeof (lifr)) == -1) { return (ipadm_errno2status(errno)); } return (IPADM_SUCCESS); } /* * Issues the ioctl SIOCSLIFNAME to kernel. If IPADM_OPT_GENPPA is set in * `ipadm_flags', then a ppa will be generated. `newif' will be updated * with the generated ppa. */ static ipadm_status_t i_ipadm_slifname(ipadm_handle_t iph, char *ifname, char *newif, uint64_t flags, int fd, uint32_t ipadm_flags) { struct lifreq lifr; ipadm_status_t status = IPADM_SUCCESS; int err = 0; sa_family_t af; int ppa; ifspec_t ifsp; boolean_t valid_if; bzero(&lifr, sizeof (lifr)); if (ipadm_flags & IPADM_OPT_GENPPA) { /* * We'd like to just set lifr_ppa to UINT_MAX and have the * kernel pick a PPA. Unfortunately, that would mishandle * two cases: * * 1. If the PPA is available but the groupname is taken * (e.g., the "ipmp2" IP interface name is available * but the "ipmp2" groupname is taken) then the * auto-assignment by the kernel will fail. * * 2. If we're creating (e.g.) an IPv6-only IPMP * interface, and there's already an IPv4-only IPMP * interface, the kernel will allow us to accidentally * reuse the IPv6 IPMP interface name (since * SIOCSLIFNAME uniqueness is per-interface-type). * This will cause administrative confusion. * * Thus, we instead take a brute-force approach of checking * whether the IPv4 or IPv6 name is already in-use before * attempting the SIOCSLIFNAME. As per (1) above, the * SIOCSLIFNAME may still fail, in which case we just proceed * to the next one. If this approach becomes too slow, we * can add a new SIOC* to handle this case in the kernel. */ for (ppa = 0; ppa < UINT_MAX; ppa++) { (void) snprintf(lifr.lifr_name, LIFNAMSIZ, "%s%d", ifname, ppa); if (ioctl(iph->iph_sock, SIOCGLIFFLAGS, &lifr) != -1 || errno != ENXIO) continue; if (ioctl(iph->iph_sock6, SIOCGLIFFLAGS, &lifr) != -1 || errno != ENXIO) continue; lifr.lifr_ppa = ppa; lifr.lifr_flags = flags; err = ioctl(fd, SIOCSLIFNAME, &lifr); if (err != -1 || errno != EEXIST) break; } if (err == -1) { status = ipadm_errno2status(errno); } else { /* * PPA has been successfully established. * Update `newif' with the ppa. */ assert(newif != NULL); if (snprintf(newif, LIFNAMSIZ, "%s%d", ifname, ppa) >= LIFNAMSIZ) return (IPADM_INVALID_ARG); } } else { /* We should have already validated the interface name. */ valid_if = ifparse_ifspec(ifname, &ifsp); assert(valid_if); /* * Before we call SIOCSLIFNAME, ensure that the IPMP group * interface for this address family exists. Otherwise, the * kernel will kick the interface out of the group when we do * the SIOCSLIFNAME. * * Example: suppose bge0 is plumbed for IPv4 and in group "a". * If we're now plumbing bge0 for IPv6, but the IPMP group * interface for "a" is not plumbed for IPv6, the SIOCSLIFNAME * will kick bge0 out of group "a", which is undesired. */ if (flags & IFF_IPV4) af = AF_INET; else af = AF_INET6; status = i_ipadm_create_ipmp_peer(iph, ifname, af); if (status != IPADM_SUCCESS) return (status); lifr.lifr_ppa = ifsp.ifsp_ppa; lifr.lifr_flags = flags; (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (ioctl(fd, SIOCSLIFNAME, &lifr) == -1) status = ipadm_errno2status(errno); } return (status); } /* * Plumbs the interface `ifname' for the address family `af'. It also persists * the interface for `af' if IPADM_OPT_PERSIST is set in `ipadm_flags'. */ ipadm_status_t i_ipadm_plumb_if(ipadm_handle_t iph, char *ifname, sa_family_t af, uint32_t ipadm_flags) { int ip_muxid; int mux_fd = -1, ip_fd, arp_fd; char *udp_dev_name; dlpi_handle_t dh_arp = NULL, dh_ip; uint64_t ifflags; struct lifreq lifr; uint_t dlpi_flags; ipadm_status_t status = IPADM_SUCCESS; char *linkname; boolean_t legacy = (iph->iph_flags & IPH_LEGACY); zoneid_t zoneid; char newif[LIFNAMSIZ]; char lifname[LIFNAMSIZ]; datalink_id_t linkid; int sock; boolean_t islo; boolean_t is_persistent = ((ipadm_flags & IPADM_OPT_PERSIST) != 0); uint32_t dlflags; dladm_status_t dlstatus; if (iph->iph_dlh != NULL) { dlstatus = dladm_name2info(iph->iph_dlh, ifname, &linkid, &dlflags, NULL, NULL); } /* * If we're in the global zone and we're plumbing a datalink, make * sure that the datalink is not assigned to a non-global zone. Note * that the non-global zones don't need this check, because zoneadm * has taken care of this when the zones boot. */ if (iph->iph_zoneid == GLOBAL_ZONEID && dlstatus == DLADM_STATUS_OK) { zoneid = ALL_ZONES; if (zone_check_datalink(&zoneid, linkid) == 0) { /* interface is in use by a non-global zone. */ return (IPADM_IF_INUSE); } } /* loopback interfaces are just added as logical interface */ bzero(&lifr, sizeof (lifr)); islo = i_ipadm_is_loopback(ifname); if (islo || i_ipadm_get_lnum(ifname) != 0) { (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (af == AF_INET) sock = iph->iph_sock; else sock = iph->iph_sock6; if (islo && ioctl(sock, SIOCGLIFADDR, (caddr_t)&lifr) >= 0) return (IPADM_IF_EXISTS); if (ioctl(sock, SIOCLIFADDIF, (caddr_t)&lifr) < 0) return (ipadm_errno2status(errno)); /* * By default, kernel configures 127.0.0.1 on the loopback * interface. Replace this with 0.0.0.0 to be consistent * with interface creation on other physical interfaces. */ if (islo && !legacy) { bzero(&lifr.lifr_addr, sizeof (lifr.lifr_addr)); lifr.lifr_addr.ss_family = af; if (ioctl(sock, SIOCSLIFADDR, (caddr_t)&lifr) < 0) return (ipadm_errno2status(errno)); if (is_persistent) { status = i_ipadm_persist_if(iph, ifname, af, ipadm_flags); if (status != IPADM_SUCCESS) { (void) i_ipadm_delete_if(iph, ifname, af, IPADM_OPT_ACTIVE); } } } return (status); } dlpi_flags = DLPI_NOATTACH; /* * If IPADM_OPT_IPMP is specified, then this is a request * to create an IPMP interface atop /dev/ipmpstub0. (We can't simply * pass "ipmpstub0" as devname since an admin *could* have a normal * vanity-named link named "ipmpstub0" that they'd like to plumb.) */ if (ipadm_flags & IPADM_OPT_IPMP) { dlpi_flags |= DLPI_DEVONLY; linkname = "ipmpstub0"; } else { /* * Verify that the user is not creating a persistent * IP interface on a non-persistent data-link. */ if (!i_ipadm_is_vni(ifname) && dlstatus == DLADM_STATUS_OK && is_persistent && !(dlflags & DLADM_OPT_PERSIST)) { return (IPADM_TEMPORARY_OBJ); } linkname = ifname; } /* * We use DLPI_NOATTACH because the ip module will do the attach * itself for DLPI style-2 devices. */ if (dlpi_open(linkname, &dh_ip, dlpi_flags) != DLPI_SUCCESS) return (IPADM_DLPI_FAILURE); ip_fd = dlpi_fd(dh_ip); if (ioctl(ip_fd, I_PUSH, IP_MOD_NAME) == -1) { status = ipadm_errno2status(errno); goto done; } /* * Set IFF_IPV4/IFF_IPV6 flags. The kernel only allows modifications * to IFF_IPv4, IFF_IPV6, IFF_BROADCAST, IFF_XRESOLV, IFF_NOLINKLOCAL. */ ifflags = 0; /* Set the name string and the IFF_IPV* flag */ if (af == AF_INET) { ifflags = IFF_IPV4; } else { ifflags = IFF_IPV6; /* * With the legacy method, the link-local address should be * configured as part of the interface plumb, using the default * token. If IPH_LEGACY is not specified, we want to set :: as * the address and require the admin to explicitly call * ipadm_create_addr() with the address object type set to * IPADM_ADDR_IPV6_ADDRCONF to create the link-local address * as well as the autoconfigured addresses. */ if (!legacy && !i_ipadm_is_6to4(iph, ifname)) ifflags |= IFF_NOLINKLOCAL; } (void) strlcpy(newif, ifname, sizeof (newif)); status = i_ipadm_slifname(iph, ifname, newif, ifflags, ip_fd, ipadm_flags); if (status != IPADM_SUCCESS) goto done; /* Get the full set of existing flags for this stream */ status = i_ipadm_get_flags(iph, newif, af, &ifflags); if (status != IPADM_SUCCESS) goto done; udp_dev_name = (af == AF_INET6 ? UDP6_DEV_NAME : UDP_DEV_NAME); status = ipadm_open_arp_on_udp(udp_dev_name, &mux_fd); if (status != IPADM_SUCCESS) goto done; /* Check if arp is not needed */ if (ifflags & (IFF_NOARP|IFF_IPV6)) { /* * PLINK the interface stream so that the application can exit * without tearing down the stream. */ if ((ip_muxid = ioctl(mux_fd, I_PLINK, ip_fd)) == -1) status = ipadm_errno2status(errno); goto done; } /* * This interface does use ARP, so set up a separate stream * from the interface to ARP. * * We use DLPI_NOATTACH because the arp module will do the attach * itself for DLPI style-2 devices. */ if (dlpi_open(linkname, &dh_arp, dlpi_flags) != DLPI_SUCCESS) { status = IPADM_DLPI_FAILURE; goto done; } arp_fd = dlpi_fd(dh_arp); if (ioctl(arp_fd, I_PUSH, ARP_MOD_NAME) == -1) { status = ipadm_errno2status(errno); goto done; } status = i_ipadm_slifname_arp(newif, ifflags, arp_fd); if (status != IPADM_SUCCESS) goto done; /* * PLINK the IP and ARP streams so that ifconfig can exit * without tearing down the stream. */ if ((ip_muxid = ioctl(mux_fd, I_PLINK, ip_fd)) == -1) { status = ipadm_errno2status(errno); goto done; } if (ioctl(mux_fd, I_PLINK, arp_fd) < 0) { status = ipadm_errno2status(errno); (void) ioctl(mux_fd, I_PUNLINK, ip_muxid); } done: dlpi_close(dh_ip); if (dh_arp != NULL) dlpi_close(dh_arp); if (mux_fd != -1) (void) close(mux_fd); if (status == IPADM_SUCCESS) { /* copy back new ifname */ (void) strlcpy(ifname, newif, LIFNAMSIZ); /* * If it is a 6to4 tunnel, create a default * addrobj name for the default address on the 0'th * logical interface and set IFF_UP in the interface flags. */ if (i_ipadm_is_6to4(iph, ifname)) { struct ipadm_addrobj_s addr; i_ipadm_init_addr(&addr, ifname, "", IPADM_ADDR_STATIC); addr.ipadm_af = af; status = i_ipadm_lookupadd_addrobj(iph, &addr); if (status != IPADM_SUCCESS) return (status); status = ipadm_add_aobjname(iph, ifname, af, addr.ipadm_aobjname, IPADM_ADDR_STATIC, 0); if (status != IPADM_SUCCESS) return (status); addr.ipadm_lifnum = 0; i_ipadm_addrobj2lifname(&addr, lifname, sizeof (lifname)); status = i_ipadm_set_flags(iph, lifname, af, IFF_UP, 0); if (status != IPADM_SUCCESS) return (status); } else { /* * Prevent static IPv6 addresses from triggering * autoconf. This does not have to be done for * 6to4 tunnel interfaces, since in.ndpd will * not autoconfigure those interfaces. */ if (af == AF_INET6 && !legacy) (void) i_ipadm_disable_autoconf(newif); } /* * If IPADM_OPT_PERSIST was set in flags, store the * interface in persistent DB. */ if (is_persistent) { status = i_ipadm_persist_if(iph, newif, af, ipadm_flags); if (status != IPADM_SUCCESS) { (void) i_ipadm_delete_if(iph, newif, af, IPADM_OPT_ACTIVE); } } } if (status == IPADM_EXISTS) status = IPADM_IF_EXISTS; return (status); } /* * Unplumbs the interface in `ifname' of family `af'. */ ipadm_status_t i_ipadm_unplumb_if(ipadm_handle_t iph, const char *ifname, sa_family_t af) { int ip_muxid, arp_muxid; int mux_fd = -1; int muxid_fd = -1; char *udp_dev_name; uint64_t flags; boolean_t changed_arp_muxid = B_FALSE; int save_errno; struct lifreq lifr; ipadm_status_t ret = IPADM_SUCCESS; int sock; lifgroupinfo_t lifgr; ifaddrlistx_t *ifaddrs, *ifaddrp; boolean_t v6 = (af == AF_INET6); /* Just do SIOCLIFREMOVEIF on loopback interfaces */ bzero(&lifr, sizeof (lifr)); if (i_ipadm_is_loopback(ifname) || (i_ipadm_get_lnum(ifname) != 0 && (iph->iph_flags & IPH_LEGACY))) { (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (ioctl((af == AF_INET) ? iph->iph_sock : iph->iph_sock6, SIOCLIFREMOVEIF, (caddr_t)&lifr) < 0) { return (ipadm_errno2status(errno)); } return (IPADM_SUCCESS); } /* * We used /dev/udp or udp6 to set up the mux. So we have to use * the same now for PUNLINK also. */ if (v6) { udp_dev_name = UDP6_DEV_NAME; sock = iph->iph_sock6; } else { udp_dev_name = UDP_DEV_NAME; sock = iph->iph_sock; } if ((muxid_fd = open(udp_dev_name, O_RDWR)) == -1) { ret = ipadm_errno2status(errno); goto done; } ret = ipadm_open_arp_on_udp(udp_dev_name, &mux_fd); if (ret != IPADM_SUCCESS) goto done; (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (ioctl(muxid_fd, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) { ret = ipadm_errno2status(errno); goto done; } flags = lifr.lifr_flags; again: if (flags & IFF_IPMP) { /* * There are two reasons the I_PUNLINK can fail with EBUSY: * (1) if IP interfaces are in the group, or (2) if IPMP data * addresses are administratively up. For case (1), we fail * here with a specific error message. For case (2), we bring * down the addresses prior to doing the I_PUNLINK. If the * I_PUNLINK still fails with EBUSY then the configuration * must have changed after our checks, in which case we branch * back up to `again' and rerun this logic. The net effect is * that unplumbing an IPMP interface will only fail with EBUSY * if IP interfaces are in the group. */ if (ioctl(sock, SIOCGLIFGROUPNAME, &lifr) == -1) { ret = ipadm_errno2status(errno); goto done; } (void) strlcpy(lifgr.gi_grname, lifr.lifr_groupname, LIFGRNAMSIZ); if (ioctl(sock, SIOCGLIFGROUPINFO, &lifgr) == -1) { ret = ipadm_errno2status(errno); goto done; } if ((v6 && lifgr.gi_nv6 != 0) || (!v6 && lifgr.gi_nv4 != 0)) { ret = IPADM_GRP_NOTEMPTY; goto done; } /* * The kernel will fail the I_PUNLINK if the IPMP interface * has administratively up addresses; bring them down. */ if (ifaddrlistx(ifname, IFF_UP|IFF_DUPLICATE, 0, &ifaddrs) == -1) { ret = ipadm_errno2status(errno); goto done; } ifaddrp = ifaddrs; for (; ifaddrp != NULL; ifaddrp = ifaddrp->ia_next) { int sock = (ifaddrp->ia_flags & IFF_IPV4) ? iph->iph_sock : iph->iph_sock6; struct lifreq lifrl; if (((ifaddrp->ia_flags & IFF_IPV6) && !v6) || (!(ifaddrp->ia_flags & IFF_IPV6) && v6)) continue; bzero(&lifrl, sizeof (lifrl)); (void) strlcpy(lifrl.lifr_name, ifaddrp->ia_name, sizeof (lifrl.lifr_name)); if (ioctl(sock, SIOCGLIFFLAGS, &lifrl) < 0) { ret = ipadm_errno2status(errno); ifaddrlistx_free(ifaddrs); goto done; } if (lifrl.lifr_flags & IFF_UP) { ret = i_ipadm_set_flags(iph, lifrl.lifr_name, ((lifrl.lifr_flags & IFF_IPV4) ? AF_INET : AF_INET6), 0, IFF_UP); if (ret != IPADM_SUCCESS) { ifaddrlistx_free(ifaddrs); goto done; } } else if (lifrl.lifr_flags & IFF_DUPLICATE) { if (ioctl(sock, SIOCGLIFADDR, &lifrl) < 0 || ioctl(sock, SIOCSLIFADDR, &lifrl) < 0) { ret = ipadm_errno2status(errno); ifaddrlistx_free(ifaddrs); goto done; } } } ifaddrlistx_free(ifaddrs); } if (ioctl(muxid_fd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) { ret = ipadm_errno2status(errno); goto done; } arp_muxid = lifr.lifr_arp_muxid; ip_muxid = lifr.lifr_ip_muxid; /* * We don't have a good way of knowing whether the arp stream is * plumbed. We can't rely on IFF_NOARP because someone could * have turned it off later using "ifconfig xxx -arp". */ if (arp_muxid != 0) { if (ioctl(mux_fd, I_PUNLINK, arp_muxid) < 0) { /* * See the comment before the SIOCGLIFGROUPNAME call. */ if (errno == EBUSY && (flags & IFF_IPMP)) goto again; if ((errno == EINVAL) && (flags & (IFF_NOARP | IFF_IPV6))) { /* * Some plumbing utilities set the muxid to * -1 or some invalid value to signify that * there is no arp stream. Set the muxid to 0 * before trying to unplumb the IP stream. * IP does not allow the IP stream to be * unplumbed if it sees a non-null arp muxid, * for consistency of IP-ARP streams. */ lifr.lifr_arp_muxid = 0; (void) ioctl(muxid_fd, SIOCSLIFMUXID, (caddr_t)&lifr); changed_arp_muxid = B_TRUE; } /* * In case of any other error, we continue with * the unplumb. */ } } if (ioctl(mux_fd, I_PUNLINK, ip_muxid) < 0) { if (changed_arp_muxid) { /* * Some error occurred, and we need to restore * everything back to what it was. */ save_errno = errno; lifr.lifr_arp_muxid = arp_muxid; lifr.lifr_ip_muxid = ip_muxid; (void) ioctl(muxid_fd, SIOCSLIFMUXID, (caddr_t)&lifr); errno = save_errno; } /* * See the comment before the SIOCGLIFGROUPNAME call. */ if (errno == EBUSY && (flags & IFF_IPMP)) goto again; ret = ipadm_errno2status(errno); } done: if (muxid_fd != -1) (void) close(muxid_fd); if (mux_fd != -1) (void) close(mux_fd); if (af == AF_INET6 && ret == IPADM_SUCCESS) { /* * in.ndpd maintains the phyints in its memory even after * the interface is plumbed, so that it can be reused when * the interface gets plumbed again. The default behavior * of in.ndpd is to start autoconfiguration for an interface * that gets plumbed. We need to send the * message IPADM_ENABLE_AUTOCONF to in.ndpd to restore this * default behavior on replumb. */ (void) i_ipadm_enable_autoconf(ifname); } return (ret); } /* * Saves the given interface name `ifname' with address family `af' in * persistent DB. */ static ipadm_status_t i_ipadm_persist_if(ipadm_handle_t iph, const char *ifname, sa_family_t af, uint32_t ipadm_flags) { ipmgmt_if_arg_t ifarg; int err; (void) strlcpy(ifarg.ia_ifname, ifname, sizeof (ifarg.ia_ifname)); ifarg.ia_family = af; if (ipadm_flags & IPADM_OPT_IPMP) ifarg.ia_ifclass = IPADM_IF_CLASS_IPMP; else ifarg.ia_ifclass = IPADM_IF_CLASS_REGULAR; ifarg.ia_cmd = IPMGMT_CMD_SETIF; ifarg.ia_flags = IPMGMT_PERSIST; err = ipadm_door_call(iph, &ifarg, sizeof (ifarg), NULL, 0, B_FALSE); return (ipadm_errno2status(err)); } /* * Remove the IP interface from active configuration. If IPADM_OPT_PERSIST * is set in `ipadm_flags', it is also removed from persistent configuration. */ ipadm_status_t i_ipadm_delete_if(ipadm_handle_t iph, const char *ifname, sa_family_t af, uint32_t ipadm_flags) { ipadm_status_t ret = IPADM_SUCCESS; ipadm_status_t db_status; char tmp_ifname[LIFNAMSIZ]; char *cp; struct ipadm_addrobj_s ipaddr; boolean_t is_persistent = (ipadm_flags & IPADM_OPT_PERSIST); ret = i_ipadm_unplumb_if(iph, ifname, af); if (ret != IPADM_SUCCESS) goto done; cp = strrchr(ifname, IPADM_LOGICAL_SEP); if (cp != NULL) { assert(iph->iph_flags & IPH_LEGACY); /* * This is a non-zero logical interface. * Find the addrobj and remove it from the daemon's memory. */ (void) strlcpy(tmp_ifname, ifname, sizeof (tmp_ifname)); tmp_ifname[cp - ifname] = '\0'; *cp++ = '\0'; ipaddr.ipadm_lifnum = atoi(cp); (void) strlcpy(ipaddr.ipadm_ifname, tmp_ifname, sizeof (ipaddr.ipadm_ifname)); ipaddr.ipadm_af = af; ret = i_ipadm_get_lif2addrobj(iph, &ipaddr); if (ret == IPADM_SUCCESS) { ret = i_ipadm_delete_addrobj(iph, &ipaddr, IPADM_OPT_ACTIVE); } else if (ret == IPADM_NOTFOUND) { ret = IPADM_SUCCESS; } return (ret); } done: /* * Even if interface does not exist, remove all its addresses and * properties from the persistent store. If interface does not * exist both in kernel and the persistent store, return IPADM_ENXIO. */ if ((ret == IPADM_ENXIO && is_persistent) || ret == IPADM_SUCCESS) { db_status = i_ipadm_delete_ifobj(iph, ifname, af, is_persistent); if (db_status == IPADM_SUCCESS) ret = IPADM_SUCCESS; } return (ret); } /* * Resets all addresses on interface `ifname' with address family `af' * from ipmgmtd daemon. If is_persistent = B_TRUE, all interface properties * and address objects of `ifname' for `af' are also removed from the * persistent DB. */ ipadm_status_t i_ipadm_delete_ifobj(ipadm_handle_t iph, const char *ifname, sa_family_t af, boolean_t is_persistent) { ipmgmt_if_arg_t ifarg; int err; ifarg.ia_cmd = IPMGMT_CMD_RESETIF; ifarg.ia_flags = IPMGMT_ACTIVE; if (is_persistent) ifarg.ia_flags |= IPMGMT_PERSIST; ifarg.ia_family = af; (void) strlcpy(ifarg.ia_ifname, ifname, LIFNAMSIZ); err = ipadm_door_call(iph, &ifarg, sizeof (ifarg), NULL, 0, B_FALSE); return (ipadm_errno2status(err)); } /* * Create the interface by plumbing it for IP. * This function will check if there is saved configuration information * for `ifname' and return IPADM_OP_DISABLE_OBJ if the name-space * for `ifname' is taken. */ ipadm_status_t i_ipadm_create_if(ipadm_handle_t iph, char *ifname, sa_family_t af, uint32_t ipadm_flags) { ipadm_status_t status; boolean_t p_exists; sa_family_t other_af; /* * Return error, if the interface already exists in either the active * or the persistent configuration. */ if (ipadm_if_enabled(iph, ifname, af)) return (IPADM_IF_EXISTS); if (!(iph->iph_flags & IPH_LEGACY)) { status = i_ipadm_if_pexists(iph, ifname, af, &p_exists); if (status != IPADM_SUCCESS) return (status); other_af = (af == AF_INET ? AF_INET6 : AF_INET); if (p_exists) { if (!ipadm_if_enabled(iph, ifname, other_af)) return (IPADM_OP_DISABLE_OBJ); else ipadm_flags &= ~IPADM_OPT_PERSIST; } } return (i_ipadm_plumb_if(iph, ifname, af, ipadm_flags)); } /* * Plumbs an interface. Creates both IPv4 and IPv6 interfaces by * default, unless a value in `af' is specified. The interface may be plumbed * only if there is no previously saved persistent configuration information * for the interface (in which case the ipadm_enable_if() function must * be used to enable the interface). * * Returns: IPADM_SUCCESS, IPADM_FAILURE, IPADM_IF_EXISTS, * IPADM_IF_PERSIST_EXISTS, IPADM_DLPI_FAILURE, * or appropriate ipadm_status_t corresponding to the errno. * * `ifname' must point to memory that can hold upto LIFNAMSIZ chars. It may * be over-written with the actual interface name when a PPA has to be * internally generated by the library. */ ipadm_status_t ipadm_create_if(ipadm_handle_t iph, char *ifname, sa_family_t af, uint32_t flags) { ipadm_status_t status; boolean_t created_v4 = B_FALSE; char newifname[LIFNAMSIZ]; /* Check for the required authorization */ if (!ipadm_check_auth()) return (IPADM_EAUTH); if (flags == 0 || ((flags & IPADM_OPT_PERSIST) && !(flags & IPADM_OPT_ACTIVE)) || (flags & ~(IPADM_COMMON_OPT_MASK | IPADM_OPT_IPMP | IPADM_OPT_GENPPA))) { return (IPADM_INVALID_ARG); } if (flags & IPADM_OPT_GENPPA) { if (snprintf(newifname, LIFNAMSIZ, "%s0", ifname) >= LIFNAMSIZ) return (IPADM_INVALID_ARG); } else { if (strlcpy(newifname, ifname, LIFNAMSIZ) >= LIFNAMSIZ) return (IPADM_INVALID_ARG); } if (!i_ipadm_validate_ifname(iph, newifname)) return (IPADM_INVALID_ARG); if ((af == AF_INET || af == AF_UNSPEC) && !i_ipadm_is_6to4(iph, ifname)) { status = i_ipadm_create_if(iph, ifname, AF_INET, flags); if (status != IPADM_SUCCESS) return (status); created_v4 = B_TRUE; } if (af == AF_INET6 || af == AF_UNSPEC) { status = i_ipadm_create_if(iph, ifname, AF_INET6, flags); if (status != IPADM_SUCCESS) { if (created_v4) { (void) i_ipadm_delete_if(iph, ifname, AF_INET, IPADM_OPT_ACTIVE); } return (status); } } return (IPADM_SUCCESS); } ipadm_status_t ipadm_add_ipmp_member(ipadm_handle_t iph, const char *gifname, const char *mifname, uint32_t ipadm_flags) { return (i_ipadm_update_ipmp(iph, gifname, mifname, ipadm_flags, IPADM_ADD_IPMP)); } ipadm_status_t ipadm_remove_ipmp_member(ipadm_handle_t iph, const char *gifname, const char *mifname, uint32_t ipadm_flags) { return (i_ipadm_update_ipmp(iph, gifname, mifname, ipadm_flags, IPADM_REMOVE_IPMP)); } /* * Updates active IPMP configuration according to the specified * command. It also persists the configuration if IPADM_OPT_PERSIST * is set in `ipadm_flags'. */ static ipadm_status_t i_ipadm_update_ipmp(ipadm_handle_t iph, const char *gifname, const char *mifname, uint32_t ipadm_flags, ipadm_ipmp_op_t op) { ipadm_status_t status; char groupname1[LIFGRNAMSIZ]; char groupname2[LIFGRNAMSIZ]; /* Check for the required authorization */ if (!ipadm_check_auth()) return (IPADM_EAUTH); if (!(ipadm_flags & IPADM_OPT_ACTIVE) || gifname == NULL || mifname == NULL) return (IPADM_INVALID_ARG); if (!ipadm_if_enabled(iph, gifname, AF_UNSPEC) || !ipadm_if_enabled(iph, mifname, AF_UNSPEC)) return (IPADM_OP_DISABLE_OBJ); if (!i_ipadm_is_ipmp(iph, gifname)) return (IPADM_INVALID_ARG); if (op == IPADM_ADD_IPMP && i_ipadm_is_under_ipmp(iph, mifname)) return (IPADM_IF_INUSE); if ((status = i_ipadm_get_groupname_active(iph, gifname, groupname2, sizeof (groupname2))) != IPADM_SUCCESS) return (status); if (op == IPADM_REMOVE_IPMP) { if ((status = i_ipadm_get_groupname_active(iph, mifname, groupname1, sizeof (groupname1))) != IPADM_SUCCESS) return (status); if (groupname1[0] == '\0' || strcmp(groupname1, groupname2) != 0) return (IPADM_INVALID_ARG); groupname2[0] = '\0'; } if ((ipadm_flags & IPADM_OPT_PERSIST) && (status = i_ipadm_persist_update_ipmp(iph, gifname, mifname, op)) != IPADM_SUCCESS) return (status); return (i_ipadm_set_groupname_active(iph, mifname, groupname2)); } /* * Call the ipmgmtd to update the IPMP configuration in ipadm DB. * After this call the DB will know that mifname is under gifname and * gifname has a member, which name is mifname. */ static ipadm_status_t i_ipadm_persist_update_ipmp(ipadm_handle_t iph, const char *gifname, const char *mifname, ipadm_ipmp_op_t op) { ipmgmt_ipmp_update_arg_t args; int err; assert(op == IPADM_ADD_IPMP || op == IPADM_REMOVE_IPMP); bzero(&args, sizeof (ipmgmt_ipmp_update_arg_t)); args.ia_cmd = IPMGMT_CMD_IPMP_UPDATE; (void) strlcpy(args.ia_gifname, gifname, sizeof (args.ia_gifname)); (void) strlcpy(args.ia_mifname, mifname, sizeof (args.ia_mifname)); if (op == IPADM_ADD_IPMP) args.ia_flags = IPMGMT_APPEND; else args.ia_flags = IPMGMT_REMOVE; args.ia_flags |= IPMGMT_PERSIST; err = ipadm_door_call(iph, &args, sizeof (args), NULL, 0, B_FALSE); return (ipadm_errno2status(err)); } /* * Deletes the interface in `ifname'. Removes both IPv4 and IPv6 interfaces * when `af' = AF_UNSPEC. */ ipadm_status_t ipadm_delete_if(ipadm_handle_t iph, const char *ifname, sa_family_t af, uint32_t flags) { ipadm_status_t status1 = IPADM_SUCCESS; ipadm_status_t status2 = IPADM_SUCCESS; ipadm_status_t other; /* Check for the required authorization */ if (!ipadm_check_auth()) return (IPADM_EAUTH); /* Validate the `ifname' for any logical interface. */ if (flags == 0 || (flags & ~(IPADM_COMMON_OPT_MASK)) || !i_ipadm_validate_ifname(iph, ifname)) return (IPADM_INVALID_ARG); if (af == AF_INET || af == AF_UNSPEC) status1 = i_ipadm_delete_if(iph, ifname, AF_INET, flags); if (af == AF_INET6 || af == AF_UNSPEC) status2 = i_ipadm_delete_if(iph, ifname, AF_INET6, flags); /* * If the family has been uniquely identified, we return the * associated status, even if that is ENXIO. Calls from ifconfig * which can only unplumb one of IPv4/IPv6 at any time fall under * this category. */ if (af == AF_INET) return (status1); else if (af == AF_INET6) return (status2); else if (af != AF_UNSPEC) return (IPADM_INVALID_ARG); /* * If af is AF_UNSPEC, then we return the following: * status1, if status1 == status2 * IPADM_SUCCESS, if either of status1 or status2 is SUCCESS * and the other status is ENXIO * IPADM_ENXIO, if both status1 and status2 are ENXIO * IPADM_FAILURE otherwise. */ if (status1 == status2) { /* covers the case when both status1 and status2 are ENXIO */ return (status1); } else if (status1 == IPADM_SUCCESS || status2 == IPADM_SUCCESS) { if (status1 == IPADM_SUCCESS) other = status2; else other = status1; return (other == IPADM_ENXIO ? IPADM_SUCCESS : IPADM_FAILURE); } else { return (IPADM_FAILURE); } } /* * Returns information about all interfaces in both active and persistent * configuration. If `ifname' is not NULL, it returns only the interface * identified by `ifname'. * * Return values: * On success: IPADM_SUCCESS. * On error : IPADM_INVALID_ARG, IPADM_ENXIO or IPADM_FAILURE. */ ipadm_status_t ipadm_if_info(ipadm_handle_t iph, const char *ifname, ipadm_if_info_t **if_info, uint32_t flags, int64_t lifc_flags) { ipadm_status_t status; ifspec_t ifsp; if (if_info == NULL || iph == NULL || flags != 0) return (IPADM_INVALID_ARG); if (ifname != NULL && (!ifparse_ifspec(ifname, &ifsp) || ifsp.ifsp_lunvalid)) { return (IPADM_INVALID_ARG); } status = i_ipadm_get_all_if_info(iph, ifname, if_info, lifc_flags); if (status != IPADM_SUCCESS) return (status); if (ifname != NULL && *if_info == NULL) return (IPADM_ENXIO); return (IPADM_SUCCESS); } /* * Frees the linked list allocated by ipadm_if_info(). */ void ipadm_free_if_info(ipadm_if_info_t *ifinfo) { ipadm_if_info_t *ifinfo_next; for (; ifinfo != NULL; ifinfo = ifinfo_next) { ifinfo_next = ifinfo->ifi_next; i_ipadm_free_ipmp_members(&ifinfo->ifi_ipmp_cmembers); i_ipadm_free_ipmp_members(&ifinfo->ifi_ipmp_pmembers); free(ifinfo); } } static void i_ipadm_free_ipmp_members(ipadm_ipmp_members_t *ipmp_members) { ipadm_ipmp_member_t *ipmp_member; while ((ipmp_member = list_remove_head(ipmp_members)) != NULL) free(ipmp_member); list_destroy(ipmp_members); } /* * Re-enable the interface `ifname' based on the saved configuration * for `ifname'. */ ipadm_status_t ipadm_enable_if(ipadm_handle_t iph, const char *ifname, uint32_t flags) { boolean_t set_init = B_FALSE; nvlist_t *ifnvl; ipadm_status_t status; ifspec_t ifsp; /* Check for the required authorization */ if (!ipadm_check_auth()) return (IPADM_EAUTH); /* Check for logical interfaces. */ if (!ifparse_ifspec(ifname, &ifsp) || ifsp.ifsp_lunvalid) return (IPADM_INVALID_ARG); /* Enabling an interface persistently is not supported. */ if (flags & IPADM_OPT_PERSIST) return (IPADM_NOTSUP); /* * Return early by checking if the interface is already enabled. */ if (ipadm_if_enabled(iph, ifname, AF_INET) && ipadm_if_enabled(iph, ifname, AF_INET6)) return (IPADM_IF_EXISTS); /* * Enable the interface and restore all its interface properties * and address objects. */ status = i_ipadm_init_ifs(iph, ifname, &ifnvl); if (status != IPADM_SUCCESS) return (status); assert(ifnvl != NULL); /* * ipadm_enable_if() does exactly what ipadm_init_ifs() does, * but only for one interface. We need to set IPH_INIT because * ipmgmtd daemon does not have to write the interface to the * persistent db. The interface is already available in the * persistent db and we are here to re-enable the persistent * configuration. * * But we need to make sure we're not accidentally clearing an * IPH_INIT flag that was already set when we were called. */ if ((iph->iph_flags & IPH_INIT) == 0) { iph->iph_flags |= IPH_INIT; set_init = B_TRUE; } status = i_ipadm_init_ifobj(iph, ifname, ifnvl); if (set_init) iph->iph_flags &= ~IPH_INIT; nvlist_free(ifnvl); return (status); } /* * Disable the interface `ifname' by removing it from the active configuration. * Error code return values follow the model in ipadm_delete_if() */ ipadm_status_t ipadm_disable_if(ipadm_handle_t iph, const char *ifname, uint32_t flags) { ipadm_status_t status1, status2, other; ifspec_t ifsp; /* Check for the required authorization */ if (!ipadm_check_auth()) return (IPADM_EAUTH); /* Check for logical interfaces. */ if (!ifparse_ifspec(ifname, &ifsp) || ifsp.ifsp_lunvalid) return (IPADM_INVALID_ARG); /* Disabling an interface persistently is not supported. */ if (flags & IPADM_OPT_PERSIST) return (IPADM_NOTSUP); status1 = i_ipadm_unplumb_if(iph, ifname, AF_INET6); if (status1 == IPADM_SUCCESS) status1 = i_ipadm_delete_ifobj(iph, ifname, AF_INET6, B_FALSE); status2 = i_ipadm_unplumb_if(iph, ifname, AF_INET); if (status2 == IPADM_SUCCESS) status2 = i_ipadm_delete_ifobj(iph, ifname, AF_INET, B_FALSE); if (status1 == status2) { return (status2); } else if (status1 == IPADM_SUCCESS || status2 == IPADM_SUCCESS) { if (status1 == IPADM_SUCCESS) other = status2; else other = status1; return (other == IPADM_ENXIO ? IPADM_SUCCESS : IPADM_FAILURE); } else { return (IPADM_FAILURE); } } /* * FIXME Remove this when ifconfig(1M) is updated to use IPMP support * in libipadm. */ /* * This workaround is required by ifconfig(1M) whenever an * interface is moved into an IPMP group to update the daemon's * in-memory mapping of `aobjname' to 'lifnum'. * * For `IPMGMT_ACTIVE' case, i_ipadm_delete_ifobj() would only fail if * door_call(3C) fails. Also, there is no use in returning error because * `ifname' would have been successfuly moved into IPMP group, by this time. */ void ipadm_if_move(ipadm_handle_t iph, const char *ifname) { (void) i_ipadm_delete_ifobj(iph, ifname, AF_INET, B_FALSE); (void) i_ipadm_delete_ifobj(iph, ifname, AF_INET6, B_FALSE); } ipadm_status_t i_ipadm_set_groupname_active(ipadm_handle_t iph, const char *ifname, const char *groupname) { struct lifreq lifr; ipadm_addr_info_t *addrinfo, *ia; ipadm_status_t status = IPADM_SUCCESS; (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); (void) strlcpy(lifr.lifr_groupname, groupname, sizeof (lifr.lifr_groupname)); /* Disable all addresses on the interface */ (void) i_ipadm_active_addr_info(iph, ifname, &addrinfo, IPADM_OPT_ACTIVE | IPADM_OPT_ZEROADDR, IFF_UP | IFF_DUPLICATE); for (ia = addrinfo; ia != NULL; ia = IA_NEXT(ia)) { if (strlen(ia->ia_aobjname) > 0) { (void) ipadm_disable_addr(iph, ia->ia_aobjname, 0); } else { /* * There's an address on this interfaces with no * corresponding addrobj. Just clear IFF_UP. */ (void) i_ipadm_set_flags(iph, ifname, addrinfo->ia_ifa.ifa_addr->sa_family, 0, IFF_UP); } } if (ioctl(iph->iph_sock, SIOCSLIFGROUPNAME, (caddr_t)&lifr) == -1 && ioctl(iph->iph_sock6, SIOCSLIFGROUPNAME, (caddr_t)&lifr) == -1) status = ipadm_errno2status(errno); /* Enable all addresses on the interface */ for (ia = addrinfo; ia != NULL; ia = IA_NEXT(ia)) { if (strlen(ia->ia_aobjname) > 0) { (void) ipadm_enable_addr(iph, ia->ia_aobjname, 0); } else { /* * There's an address on this interfaces with no * corresponding addrobj. Just set IFF_UP. */ (void) i_ipadm_set_flags(iph, ifname, addrinfo->ia_ifa.ifa_addr->sa_family, IFF_UP, 0); } } if (status == IPADM_SUCCESS) { if (groupname[0] == '\0') { /* * If interface was removed from IPMP group, unset the * DEPRECATED and NOFAILOVER flags. */ (void) i_ipadm_set_flags(iph, ifname, AF_INET, 0, IFF_DEPRECATED | IFF_NOFAILOVER); (void) i_ipadm_set_flags(iph, ifname, AF_INET6, 0, IFF_DEPRECATED | IFF_NOFAILOVER); } else if (addrinfo == NULL) { /* * If interface was added to IPMP group and there are no * active addresses, explicitly bring it up to be used * for link-based IPMP configuration. */ (void) i_ipadm_set_flags(iph, ifname, AF_INET, IFF_UP, 0); (void) i_ipadm_set_flags(iph, ifname, AF_INET6, IFF_UP, 0); } } ipadm_free_addr_info(addrinfo); return (status); } ipadm_status_t i_ipadm_get_groupname_active(ipadm_handle_t iph, const char *ifname, char *groupname, size_t size) { struct lifreq lifr; (void) memset(&lifr, 0, sizeof (lifr)); (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); if (ioctl(iph->iph_sock, SIOCGLIFGROUPNAME, (caddr_t)&lifr) == -1 && ioctl(iph->iph_sock6, SIOCGLIFGROUPNAME, (caddr_t)&lifr) == -1) return (ipadm_errno2status(errno)); (void) strlcpy(groupname, lifr.lifr_groupname, size); return (IPADM_SUCCESS); } /* * Returns B_TRUE if `ifname' represents an IPMP underlying interface. */ boolean_t i_ipadm_is_under_ipmp(ipadm_handle_t iph, const char *ifname) { char groupname[LIFGRNAMSIZ]; if (i_ipadm_get_groupname_active(iph, ifname, groupname, sizeof (groupname)) != IPADM_SUCCESS || groupname[0] == '\0' || strcmp(ifname, groupname) == 0) return (B_FALSE); return (B_TRUE); } /* * Returns B_TRUE if `ifname' represents an IPMP group interface. */ boolean_t i_ipadm_is_ipmp(ipadm_handle_t iph, const char *ifname) { uint64_t flags; if (i_ipadm_get_flags(iph, ifname, AF_INET, &flags) != IPADM_SUCCESS && i_ipadm_get_flags(iph, ifname, AF_INET6, &flags) != IPADM_SUCCESS) return (B_FALSE); return ((flags & IFF_IPMP) != 0); }