inetcfg.c revision 6ba597c56d749c61b4f783157f63196d7b2445f0
/*
* CDDL HEADER START
*
* The contents of this file are subject to the terms of the
* Common Development and Distribution License (the "License").
* You may not use this file except in compliance with the License.
*
* You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
* or http://www.opensolaris.org/os/licensing.
* See the License for the specific language governing permissions
* and limitations under the License.
*
* When distributing Covered Code, include this CDDL HEADER in each
* file and include the License file at usr/src/OPENSOLARIS.LICENSE.
* If applicable, add the following below this CDDL HEADER, with the
* fields enclosed by brackets "[]" replaced with your own identifying
* information: Portions Copyright [yyyy] [name of copyright owner]
*
* CDDL HEADER END
*/
/*
* Copyright 2010 Sun Microsystems, Inc. All rights reserved.
* Use is subject to license terms.
*/
#include <stdio.h>
#include <stdlib.h>
#include <string.h>
#include <errno.h>
#include <assert.h>
#include <fcntl.h>
#include <unistd.h>
#include <stropts.h>
#include <sys/sockio.h>
#include <sys/types.h>
#include <sys/socket.h>
#include <sys/varargs.h>
#include <net/route.h>
#include <netinet/in.h>
#include <inet/ip.h>
#include <arpa/inet.h>
#include <libintl.h>
#include <libdladm.h>
#include <libdllink.h>
#include <libdlpi.h>
#include <libinetutil.h>
#include <zone.h>
#include <inetcfg.h>
#define ICFG_SOCKADDR_LEN(protocol) \
(protocol == AF_INET) ? \
(socklen_t)sizeof (struct sockaddr_in) : \
(socklen_t)sizeof (struct sockaddr_in6)
#define ICFG_LOGICAL_SEP ':'
#define LOOPBACK_IF "lo0"
#define ARP_MOD_NAME "arp"
/*
* Maximum amount of time (in milliseconds) to wait for Duplicate Address
* Detection to complete in the kernel.
*/
#define DAD_WAIT_TIME 5000
/* error codes and text descriiption */
static struct icfg_error_info {
icfg_error_t error_code;
const char *error_desc;
} icfg_errors[] = {
{ ICFG_SUCCESS, "No error occurred" },
{ ICFG_FAILURE, "Generic failure" },
{ ICFG_NO_MEMORY, "Insufficient memory" },
{ ICFG_NOT_TUNNEL, "Tunnel operation attempted on non-tunnel" },
{ ICFG_NOT_SET, "Could not return non-existent value" },
{ ICFG_BAD_ADDR, "Invalid address" },
{ ICFG_BAD_PROTOCOL, "Wrong protocol family for operation" },
{ ICFG_DAD_FAILED, "Duplicate address detection failure" },
{ ICFG_DAD_FOUND, "Duplicate address detected" },
{ ICFG_IF_UP, "Interface is up" },
{ ICFG_EXISTS, "Interface already exists" },
{ ICFG_NO_EXIST, "Interface does not exist" },
{ ICFG_INVALID_ARG, "Invalid argument" },
{ ICFG_INVALID_NAME, "Invalid name" },
{ ICFG_DLPI_INVALID_LINK, "Link does not exist" },
{ ICFG_DLPI_FAILURE, "DLPI error" },
{ ICFG_NO_PLUMB_IP, "Could not plumb IP stream" },
{ ICFG_NO_PLUMB_ARP, "Could not plumb ARP stream" },
{ ICFG_NO_UNPLUMB_IP, "Could not unplumb IP stream" },
{ ICFG_NO_UNPLUMB_ARP, "Could not unplumb ARP stream" },
{ ICFG_NO_IP_MUX, "No IP mux set" },
{ 0, NULL }
};
/* convert libdlpi error to libinetcfg error */
icfg_error_t
dlpi_error_to_icfg_error(int err)
{
switch (err) {
case DLPI_SUCCESS:
return (ICFG_SUCCESS);
case DLPI_ELINKNAMEINVAL:
return (ICFG_INVALID_NAME);
case DLPI_ENOLINK:
case DLPI_EBADLINK:
return (ICFG_DLPI_INVALID_LINK);
case DLPI_EINVAL:
case DLPI_ENOTSTYLE2:
case DLPI_EBADMSG:
case DLPI_EINHANDLE:
case DLPI_EVERNOTSUP:
case DLPI_EMODENOTSUP:
return (ICFG_INVALID_ARG);
case DL_BADADDR:
return (ICFG_BAD_ADDR);
case DL_SYSERR:
switch (errno) {
case ENOMEM:
return (ICFG_NO_MEMORY);
case EINVAL:
return (ICFG_INVALID_ARG);
}
/* FALLTHROUGH */
case DLPI_FAILURE:
default:
return (ICFG_DLPI_FAILURE);
}
}
/*
* Convert a prefix length to a netmask. Note that the mask array
* should zero'ed by the caller.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
static int
prefixlen_to_mask(int prefixlen, int maxlen, uchar_t *mask)
{
if ((prefixlen < 0) || (prefixlen > maxlen)) {
errno = EINVAL;
return (ICFG_FAILURE);
}
while (prefixlen > 0) {
if (prefixlen >= 8) {
*mask++ = 0xFF;
prefixlen -= 8;
continue;
}
*mask |= 1 << (8 - prefixlen);
prefixlen--;
}
return (ICFG_SUCCESS);
}
/*
* Copies an an IPv4 or IPv6 address from a sockaddr_storage
* structure into the appropriate sockaddr structure for the
* address family (sockaddr_in for AF_INET or sockaddr_in6 for
* AF_INET6) and verifies that the structure size is large enough
* for the copy.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
static int
to_sockaddr(sa_family_t af, struct sockaddr *addr,
socklen_t *addrlen, const struct sockaddr_storage *ssaddr)
{
socklen_t len;
assert((af == AF_INET) || (af == AF_INET6));
len = ICFG_SOCKADDR_LEN(af);
if (*addrlen < len) {
errno = ENOSPC;
return (ICFG_FAILURE);
}
(void) memcpy(addr, ssaddr, len);
*addrlen = len;
return (ICFG_SUCCESS);
}
/*
* Copies an an IPv4 or IPv6 address frrom its sockaddr structure
* into a sockaddr_storage structure and does a simple size of
* structure verification.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
static int
to_sockaddr_storage(sa_family_t af, const struct sockaddr *addr,
socklen_t addrlen, struct sockaddr_storage *ssaddr)
{
socklen_t len;
assert((af == AF_INET) || (af == AF_INET6));
len = ICFG_SOCKADDR_LEN(af);
if (addrlen < len) {
errno = EINVAL;
return (ICFG_FAILURE);
}
(void) memcpy(ssaddr, addr, len);
return (ICFG_SUCCESS);
}
/*
* Return the appropriate error message for a given ICFG error.
*/
const char *
icfg_errmsg(int errcode)
{
int i;
for (i = 0; icfg_errors[i].error_desc != NULL; i++) {
if (errcode == icfg_errors[i].error_code)
return (dgettext(TEXT_DOMAIN,
icfg_errors[i].error_desc));
}
return (dgettext(TEXT_DOMAIN, "<unknown error>"));
}
/*
* Opens the an interface as defined by the interface argument and returns
* a handle to the interface via the 'handle' argument. The caller is
* responsible for freeing resources allocated by this API by calling the
* icfg_close() API.
*
* Returns: ICFG_SUCCESS, ICFG_NO_MEMORY or ICFG_FAILURE.
*/
int
icfg_open(icfg_handle_t *handle, const icfg_if_t *interface)
{
icfg_handle_t loc_handle;
int sock;
sa_family_t family;
int syserr;
/*
* Make sure that a valid protocol family was specified.
*/
if ((interface->if_protocol != AF_INET) &&
(interface->if_protocol != AF_INET6)) {
errno = EINVAL;
return (ICFG_FAILURE);
}
family = interface->if_protocol;
if ((loc_handle = calloc(1, sizeof (struct icfg_handle))) == NULL) {
return (ICFG_NO_MEMORY);
}
if ((sock = socket(family, SOCK_DGRAM, 0)) < 0) {
syserr = errno;
free(loc_handle);
errno = syserr;
return (ICFG_FAILURE);
}
loc_handle->ifh_sock = sock;
loc_handle->ifh_interface = *interface;
*handle = loc_handle;
return (ICFG_SUCCESS);
}
/*
* Closes the interface opened by icfg_open() and releases all resources
* associated with the handle.
*/
void
icfg_close(icfg_handle_t handle)
{
(void) close(handle->ifh_sock);
free(handle);
}
/*
* Retrieves the interface name associated with the handle passed in.
*/
const char *
icfg_if_name(icfg_handle_t handle)
{
return (handle->ifh_interface.if_name);
}
/*
* Retrieves the protocol associated with the handle passed in.
*/
static int
icfg_if_protocol(icfg_handle_t handle)
{
return (handle->ifh_interface.if_protocol);
}
/*
* Any time that flags are changed on an interface where either the new or the
* existing flags have IFF_UP set, we'll get at least one RTM_IFINFO message to
* announce the flag status. Typically, there are two such messages: one
* saying that the interface is going down, and another saying that it's coming
* back up.
*
* We wait here for that second message, which can take one of two forms:
* either IFF_UP or IFF_DUPLICATE. If something's amiss with the kernel,
* though, we don't wait forever. (Note that IFF_DUPLICATE is a high-order
* bit, and we can't see it in the routing socket messages.)
*/
static int
dad_wait(icfg_handle_t handle, int rtsock)
{
struct pollfd fds[1];
union {
struct if_msghdr ifm;
char buf[1024];
} msg;
int index;
int retv;
uint64_t flags;
hrtime_t starttime, now;
fds[0].fd = rtsock;
fds[0].events = POLLIN;
fds[0].revents = 0;
if ((retv = icfg_get_index(handle, &index)) != ICFG_SUCCESS)
return (retv);
starttime = gethrtime();
for (;;) {
now = gethrtime();
now = (now - starttime) / 1000000;
if (now >= DAD_WAIT_TIME)
break;
if (poll(fds, 1, DAD_WAIT_TIME - (int)now) <= 0)
break;
if (read(rtsock, &msg, sizeof (msg)) <= 0)
break;
if (msg.ifm.ifm_type != RTM_IFINFO)
continue;
/* Note that ifm_index is just 16 bits */
if (index == msg.ifm.ifm_index && (msg.ifm.ifm_flags & IFF_UP))
return (ICFG_SUCCESS);
if ((retv = icfg_get_flags(handle, &flags)) != ICFG_SUCCESS)
return (retv);
if (flags & IFF_DUPLICATE)
return (ICFG_DAD_FOUND);
}
return (ICFG_DAD_FAILED);
}
/*
* Sets the flags for the interface represented by the 'handle'
* argument to the value contained in the 'flags' argument.
*
* If the new flags value will transition the interface from "down" to "up,"
* then duplicate address detection is performed by the kernel. This routine
* waits to get the outcome of that test.
*
* Returns: ICFG_SUCCESS, ICFG_DAD_FOUND, ICFG_DAD_FAILED or ICFG_FAILURE.
*/
int
icfg_set_flags(icfg_handle_t handle, uint64_t flags)
{
struct lifreq lifr;
uint64_t oflags;
int ret;
int rtsock = -1;
int aware = RTAW_UNDER_IPMP;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if ((ret = icfg_get_flags(handle, &oflags)) != ICFG_SUCCESS)
return (ret);
if (oflags == flags)
return (ICFG_SUCCESS);
/*
* Any time flags are changed on an interface that has IFF_UP set,
* you'll get a routing socket message. We care about the status,
* though, only when the new flags are marked "up." Since we may be
* changing an IPMP test address, we enable RTAW_UNDER_IPMP.
*/
if (flags & IFF_UP) {
rtsock = socket(PF_ROUTE, SOCK_RAW, icfg_if_protocol(handle));
if (rtsock != -1) {
(void) setsockopt(rtsock, SOL_ROUTE, RT_AWARE, &aware,
sizeof (aware));
}
}
lifr.lifr_flags = flags;
if (ioctl(handle->ifh_sock, SIOCSLIFFLAGS, (caddr_t)&lifr) < 0) {
if (rtsock != -1)
(void) close(rtsock);
return (ICFG_FAILURE);
}
if (rtsock == -1) {
return (ICFG_SUCCESS);
} else {
ret = dad_wait(handle, rtsock);
(void) close(rtsock);
return (ret);
}
}
/*
* Sets the metric value for the interface represented by the
* 'handle' argument to the value contained in the 'metric'
* argument.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_set_metric(icfg_handle_t handle, int metric)
{
struct lifreq lifr;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
lifr.lifr_metric = metric;
if (ioctl(handle->ifh_sock, SIOCSLIFMETRIC, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the mtu value for the interface represented by the
* 'handle' argument to the value contained in the 'mtu'
* argument.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_set_mtu(icfg_handle_t handle, uint_t mtu)
{
struct lifreq lifr;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
lifr.lifr_mtu = mtu;
if (ioctl(handle->ifh_sock, SIOCSLIFMTU, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the index value for the interface represented by the
* 'handle' argument to the value contained in the 'index'
* argument.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_set_index(icfg_handle_t handle, int index)
{
struct lifreq lifr;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
lifr.lifr_index = index;
if (ioctl(handle->ifh_sock, SIOCSLIFINDEX, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the netmask address for the interface represented by
* 'handle'.
*
* The handle must represent an IPv4 interface.
*
* The address will be set to the value pointed to by 'addr'.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_PROTOCOL or ICFG_FAILURE.
*/
int
icfg_set_netmask(icfg_handle_t handle, const struct sockaddr_in *addr)
{
struct lifreq lifr;
int ret;
if (icfg_if_protocol(handle) != AF_INET) {
return (ICFG_BAD_PROTOCOL);
}
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
if ((ret = to_sockaddr_storage(icfg_if_protocol(handle),
(struct sockaddr *)addr, sizeof (*addr),
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = AF_INET;
if (ioctl(handle->ifh_sock, SIOCSLIFNETMASK, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the broadcast address for the interface represented by
* 'handle'.
*
* The handle must represent an IPv4 interface.
*
* The address will be set to the value pointed to by 'addr'.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_PROTOCOL or ICFG_FAILURE.
*/
int
icfg_set_broadcast(icfg_handle_t handle, const struct sockaddr_in *addr)
{
struct lifreq lifr;
int ret;
if (icfg_if_protocol(handle) != AF_INET) {
return (ICFG_BAD_PROTOCOL);
}
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
if ((ret = to_sockaddr_storage(icfg_if_protocol(handle),
(struct sockaddr *)addr, sizeof (*addr),
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = AF_INET;
if (ioctl(handle->ifh_sock, SIOCSLIFBRDADDR, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the prefixlen value for the interface represented by the handle
* argument to the value contained in the 'prefixlen' argument. The
* prefixlen is actually stored internally as a netmask value and the API
* will convert the value contained in 'prefixlen' into the correct netmask
* value according to the protocol family of the interface.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_set_prefixlen(icfg_handle_t handle, int prefixlen)
{
struct lifreq lifr;
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (icfg_if_protocol(handle) == AF_INET6) {
struct sockaddr_in6 *sin6;
int ret;
sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr;
if ((ret = prefixlen_to_mask(prefixlen, IPV6_ABITS,
(uchar_t *)&sin6->sin6_addr)) != ICFG_SUCCESS) {
return (ret);
}
} else {
struct sockaddr_in *sin;
int ret;
sin = (struct sockaddr_in *)&lifr.lifr_addr;
if ((ret = prefixlen_to_mask(prefixlen, IP_ABITS,
(uchar_t *)&sin->sin_addr)) != ICFG_SUCCESS) {
return (ret);
}
}
if (ioctl(handle->ifh_sock, SIOCSLIFNETMASK, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the address for the interface represented by 'handle'.
*
* The 'addr' argument points to either a sockaddr_in structure
* (for IPv4) or a sockaddr_in6 structure (for IPv6) that holds
* the IP address. The 'addrlen' argument gives the length of the
* 'addr' structure.
*
* If the interface is an IPv6 interface and the interface is
* already in the "up" state, then duplicate address detection
* is performed before the address is set and is set only if no
* duplicate address is detected.
*
* Returns: ICFG_SUCCESS, ICFG_FAILURE, ICFG_DAD_FOUND, ICFG_DAD_FAILED
* or ICFG_FAILURE.
*/
int
icfg_set_addr(icfg_handle_t handle, const struct sockaddr *addr,
socklen_t addrlen)
{
struct lifreq lifr;
uint64_t flags;
int ret;
int rtsock = -1;
int aware = RTAW_UNDER_IPMP;
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
if ((ret = to_sockaddr_storage(icfg_if_protocol(handle), addr, addrlen,
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
/*
* Need to check duplicate address detection results if the address is
* up. Since this may be an IPMP test address, enable RTAW_UNDER_IPMP.
*/
if ((ret = icfg_get_flags(handle, &flags)) != ICFG_SUCCESS)
return (ret);
if (flags & IFF_UP) {
rtsock = socket(PF_ROUTE, SOCK_RAW, icfg_if_protocol(handle));
if (rtsock != -1) {
(void) setsockopt(rtsock, SOL_ROUTE, RT_AWARE, &aware,
sizeof (aware));
}
}
(void) strlcpy(lifr.lifr_name, handle->ifh_interface.if_name,
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCSLIFADDR, (caddr_t)&lifr) < 0) {
if (rtsock != -1)
(void) close(rtsock);
return (ICFG_FAILURE);
}
if (rtsock == -1) {
return (ICFG_SUCCESS);
} else {
ret = dad_wait(handle, rtsock);
(void) close(rtsock);
return (ret);
}
}
/*
* Sets the token for the interface represented by 'handle'.
*
* The handle must represent an IPv6 interface.
*
* The token will be set to the value contained in 'addr' and
* its associated prefixlen will be set to 'prefixlen'.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_PROTOCOL or ICFG_FAILURE.
*/
int
icfg_set_token(icfg_handle_t handle, const struct sockaddr_in6 *addr,
int prefixlen)
{
struct lifreq lifr;
int ret;
if (icfg_if_protocol(handle) != AF_INET6) {
return (ICFG_BAD_PROTOCOL);
}
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
if ((ret = to_sockaddr_storage(icfg_if_protocol(handle),
(struct sockaddr *)addr, sizeof (*addr),
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
lifr.lifr_addrlen = prefixlen;
if (ioctl(handle->ifh_sock, SIOCSLIFTOKEN, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the subnet address for the interface represented by 'handle'.
*
* The 'addr' argument points to either a sockaddr_in structure
* (for IPv4) or a sockaddr_in6 structure (for IPv6) that holds
* the IP address. The 'addrlen' argument gives the length of the
* 'addr' structure.
*
* The prefixlen of the subnet address will be set to 'prefixlen'.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_set_subnet(icfg_handle_t handle, const struct sockaddr *addr,
socklen_t addrlen, int prefixlen)
{
struct lifreq lifr;
int ret;
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if ((ret = to_sockaddr_storage(icfg_if_protocol(handle), addr, addrlen,
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
lifr.lifr_addrlen = prefixlen;
if (ioctl(handle->ifh_sock, SIOCSLIFSUBNET, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Sets the destination address for the interface represented by
* 'handle'.
*
* The 'addr' argument points to either a sockaddr_in structure
* (for IPv4) or a sockaddr_in6 structure (for IPv6) that holds
* the IP address. The 'addrlen' argument gives the length of the
* 'addr' structure.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_set_dest_addr(icfg_handle_t handle, const struct sockaddr *addr,
socklen_t addrlen)
{
struct lifreq lifr;
int ret;
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if ((ret = to_sockaddr_storage(icfg_if_protocol(handle), addr, addrlen,
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
if (ioctl(handle->ifh_sock, SIOCSLIFDSTADDR, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
return (ICFG_SUCCESS);
}
/*
* Returns the address and prefixlen of the interface represented
* by 'handle'.
*
* The 'addr' argument is a result parameter that is filled in with
* the requested address. The format of the 'addr' parameter is
* determined by the address family of the interface.
*
* The 'addrlen' argument is a value-result parameter. Initially, it
* contains the amount of space pointed to by 'addr'; on return it
* contains the length in bytes of the address returned.
*
* Note that if 'addrlen' is not large enough for the returned address
* value, then ICFG_FAILURE will be returned and errno will be set to ENOSPC.
*
* If the 'force' argument is set to B_TRUE, then non-critical errors in
* obtaining the address will be ignored and the address will be set to
* all 0's. Non-critical errors consist of EADDRNOTAVAIL, EAFNOSUPPORT,
* and ENXIO.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_get_addr(icfg_handle_t handle, struct sockaddr *addr, socklen_t *addrlen,
int *prefixlen, boolean_t force)
{
struct lifreq lifr;
int ret;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFADDR, (caddr_t)&lifr) < 0) {
if (force && ((errno == EADDRNOTAVAIL) ||
(errno == EAFNOSUPPORT) || (errno == ENXIO))) {
(void) memset(&lifr.lifr_addr, 0,
sizeof (lifr.lifr_addr));
} else {
return (ICFG_FAILURE);
}
}
if ((ret = to_sockaddr(icfg_if_protocol(handle), addr, addrlen,
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
*prefixlen = lifr.lifr_addrlen;
return (ICFG_SUCCESS);
}
/*
* Returns the token address and the token prefixlen of the
* interface represented by 'handle'.
*
* The 'addr' argument is a result parameter that is filled in
* with the requested address.
*
* The 'prefixlen' argument is a result paramter that is filled
* in with the token prefixlen.
*
* If the 'force' argument is set to B_TRUE, then non-critical errors in
* obtaining the token address will be ignored and the address will be set
* to all 0's. Non-critical errors consist of EADDRNOTAVAIL and EINVAL.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_PROTOCOL or ICFG_FAILURE.
*/
int
icfg_get_token(icfg_handle_t handle, struct sockaddr_in6 *addr,
int *prefixlen, boolean_t force)
{
struct lifreq lifr;
socklen_t addrlen = sizeof (*addr);
if (icfg_if_protocol(handle) != AF_INET6) {
return (ICFG_BAD_PROTOCOL);
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFTOKEN, (caddr_t)&lifr) < 0) {
if (force && ((errno == EADDRNOTAVAIL) || (errno == EINVAL))) {
(void) memset(&lifr.lifr_addr, 0,
sizeof (lifr.lifr_addr));
} else {
return (ICFG_FAILURE);
}
}
*prefixlen = lifr.lifr_addrlen;
return (to_sockaddr(icfg_if_protocol(handle), (struct sockaddr *)addr,
&addrlen, &lifr.lifr_addr));
}
/*
* Returns the subnet address and the subnet prefixlen of the interface
* represented by 'handle'.
*
* The 'addr' argument is a result parameter that is filled in with
* the requested address. The format of the 'addr' parameter is
* determined by the address family of the interface.
*
* The 'addrlen' argument is a value-result parameter. Initially, it
* contains the amount of space pointed to by 'addr'; on return it
* contains the length in bytes of the address returned.
*
* Note that if 'addrlen' is not large enough for the returned address
* value, then ICFG_FAILURE will be returned and errno will be set to ENOSPC.
*
* If the 'force' argument is set to B_TRUE, then non-critical errors in
* obtaining the address will be ignored and the address will be set to all
* 0's. Non-critical errors consist of EADDRNOTAVAIL, EAFNOSUPPORT,and ENXIO.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_get_subnet(icfg_handle_t handle, struct sockaddr *addr,
socklen_t *addrlen, int *prefixlen, boolean_t force)
{
struct lifreq lifr;
int ret;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFSUBNET, (caddr_t)&lifr) < 0) {
if (force && ((errno == EADDRNOTAVAIL) ||
(errno == EAFNOSUPPORT) || (errno == ENXIO))) {
(void) memset(&lifr.lifr_addr, 0,
sizeof (lifr.lifr_addr));
} else {
return (ICFG_FAILURE);
}
}
if ((ret = to_sockaddr(icfg_if_protocol(handle), addr, addrlen,
&lifr.lifr_addr)) != ICFG_SUCCESS) {
return (ret);
}
*prefixlen = lifr.lifr_addrlen;
return (ICFG_SUCCESS);
}
/*
* Returns the netmask address of the interface represented by 'handle'.
*
* The handle must represent an IPv4 interface.
*
* The 'addr' argument is a result parameter that is filled in with
* the requested address.
*
* If no netmask address has been set for the interface, an address of
* all 0's will be returned.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_PROTOCOL or ICFG_FAILURE.
*/
int
icfg_get_netmask(icfg_handle_t handle, struct sockaddr_in *addr)
{
struct lifreq lifr;
socklen_t addrlen = sizeof (*addr);
if (icfg_if_protocol(handle) != AF_INET) {
return (ICFG_BAD_PROTOCOL);
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFNETMASK, (caddr_t)&lifr) < 0) {
if (errno != EADDRNOTAVAIL) {
return (ICFG_FAILURE);
}
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
}
return (to_sockaddr(icfg_if_protocol(handle), (struct sockaddr *)addr,
&addrlen, &lifr.lifr_addr));
}
/*
* Returns the broadcast address of the interface represented by 'handle'.
*
* The handle must represent an IPv4 interface.
*
* The 'addr' argument is a result parameter that is filled in with
* the requested address.
*
* If no broadcast address has been set for the interface, an address
* of all 0's will be returned.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_PROTOCOL or ICFG_FAILURE.
*/
int
icfg_get_broadcast(icfg_handle_t handle, struct sockaddr_in *addr)
{
struct lifreq lifr;
socklen_t addrlen = sizeof (*addr);
if (icfg_if_protocol(handle) != AF_INET) {
return (ICFG_BAD_PROTOCOL);
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFBRDADDR, (caddr_t)&lifr) < 0) {
if (errno != EADDRNOTAVAIL) {
return (ICFG_FAILURE);
}
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
}
return (to_sockaddr(icfg_if_protocol(handle), (struct sockaddr *)addr,
&addrlen, &lifr.lifr_addr));
}
/*
* Returns the destination address of the interface represented
* by 'handle'.
*
* The 'addr' argument is a result parameter that is filled in with
* the requested address. The format of the 'addr' parameter is
* determined by the address family of the interface.
*
* The 'addrlen' argument is a value-result parameter. Initially, it
* contains the amount of space pointed to by 'addr'; on return it
* contains the length in bytes of the address returned.
*
* Note that if 'addrlen' is not large enough for the returned address
* value, then ICFG_FAILURE will be returned and errno will be set to
* ENOSPC.
*
* If no destination address has been set for the interface, an address
* of all 0's will be returned.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_get_dest_addr(icfg_handle_t handle, struct sockaddr *addr,
socklen_t *addrlen)
{
struct lifreq lifr;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFDSTADDR, (caddr_t)&lifr) < 0) {
if (errno != EADDRNOTAVAIL) {
return (ICFG_FAILURE);
}
/* No destination address set yet */
(void) memset(&lifr.lifr_dstaddr, 0,
sizeof (lifr.lifr_dstaddr));
}
return (to_sockaddr(icfg_if_protocol(handle), addr, addrlen,
&lifr.lifr_addr));
}
/*
* Returns the groupname, if any, of the interface represented by the handle
* argument into the buffer pointed to by the 'groupname' argument. The size
* of the groupname buffer is expected to be of 'len' bytes in length and
* should be large enough to receive the groupname of the interface
* (i.e., LIFNAMSIZ).
*
* Returns: ICFG_SUCCESS, ICFG_NOT_SET or ICFG_FAILURE.
*/
int
icfg_get_groupname(icfg_handle_t handle, char *groupname, size_t len)
{
struct lifreq lifr;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
(void) memset(lifr.lifr_groupname, 0, sizeof (lifr.lifr_groupname));
if (ioctl(handle->ifh_sock, SIOCGLIFGROUPNAME, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
if (strlen(lifr.lifr_groupname) > 0) {
(void) strlcpy(groupname, lifr.lifr_groupname, len);
} else {
return (ICFG_NOT_SET);
}
return (ICFG_SUCCESS);
}
/*
* Returns the groupinfo, if any, associated with the group identified in
* the gi_grname field of the passed-in lifgr structure. Upon successful
* return, the lifgr structure will be populated with the associated
* group info.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
static int
icfg_get_groupinfo(icfg_handle_t handle, lifgroupinfo_t *lifgr)
{
if (ioctl(handle->ifh_sock, SIOCGLIFGROUPINFO, lifgr) < 0)
return (ICFG_FAILURE);
return (ICFG_SUCCESS);
}
/*
* Returns the flags value of the interface represented by the handle
* argument into the buffer pointed to by the 'flags' argument.
*
* Returns: ICFG_SUCCESS, ICFG_NO_EXIST or ICFG_FAILURE.
*/
int
icfg_get_flags(icfg_handle_t handle, uint64_t *flags)
{
struct lifreq lifr;
if (flags == NULL)
return (ICFG_INVALID_ARG);
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) {
if (errno == ENXIO)
return (ICFG_NO_EXIST);
else
return (ICFG_FAILURE);
}
*flags = lifr.lifr_flags;
return (ICFG_SUCCESS);
}
/*
* Returns the metric value of the interface represented by the handle
* argument into the buffer pointed to by the 'metric' argument.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_get_metric(icfg_handle_t handle, int *metric)
{
struct lifreq lifr;
if (metric == NULL)
return (ICFG_INVALID_ARG);
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFMETRIC, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
*metric = lifr.lifr_metric;
return (ICFG_SUCCESS);
}
/*
* Returns the mtu value of the interface represented by the handle
* argument into the buffer pointed to by the 'mtu' argument.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_get_mtu(icfg_handle_t handle, uint_t *mtu)
{
struct lifreq lifr;
if (mtu == NULL)
return (ICFG_INVALID_ARG);
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFMTU, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
*mtu = lifr.lifr_mtu;
return (ICFG_SUCCESS);
}
/*
* Returns the index value of the interface represented by the handle
* argument into the buffer pointed to by the 'index' argument.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_get_index(icfg_handle_t handle, int *index)
{
struct lifreq lifr;
if (index == NULL)
return (ICFG_INVALID_ARG);
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
lifr.lifr_addr.ss_family = icfg_if_protocol(handle);
if (ioctl(handle->ifh_sock, SIOCGLIFINDEX, (caddr_t)&lifr) < 0) {
return (ICFG_FAILURE);
}
*index = lifr.lifr_index;
return (ICFG_SUCCESS);
}
/*
* Walks a list of interfaces and for each interface found, the
* caller-supplied function 'callback()' is invoked. The iteration will be
* interrupted if the caller-supplied function does not return ICFG_SUCCESS.
*
* The 'proto' argument is used by the caller to define which interfaces are
* to be walked by the API. The possible values for 'proto' are AF_INET,
* AF_INET6, and AF_UNSPEC.
*
* The 'arg' argument is a pointer to caller-specific data.
*
* Returns: ICFG_SUCCESS, ICFG_FAILURE or error code returned by callback().
*/
int
icfg_iterate_if(int proto, int type, void *arg,
int (*callback)(icfg_if_t *interface, void *arg))
{
icfg_if_t *if_ids;
int len;
int i;
int ret;
ret = icfg_get_if_list(&if_ids, &len, proto, type);
if (ret != ICFG_SUCCESS) {
return (ret);
}
for (i = 0; i < len; i++) {
if ((ret = callback(&if_ids[i], arg)) != ICFG_SUCCESS) {
break;
}
}
icfg_free_if_list(if_ids);
return (ret);
}
/*
* Returns a list of currently plumbed interfaces. The list of interfaces is
* returned as an array of icfg_if_t structures. The number of interfaces in
* the array will be returned via the 'numif' argument. Since the array of
* interfaces is allocated by this API, the caller is responsible for freeing
* the memory associated with this array by calling icfg_free_if_list().
*
* The 'proto' argument is used by the caller to define which interfaces are
* to be listed by the API. The possible values for proto are AF_INET,
* AF_INET6, and AF_UNSPEC.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_PROTOCOL, ICFG_NO_MEMORY or ICFG_FAILURE.
*/
static int
get_plumbed_if_list(icfg_if_t **list, int *numif, int proto) {
int sock;
struct lifconf lifc;
struct lifnum lifn;
struct lifreq *lifrp;
char *buf;
unsigned bufsize;
icfg_if_t *loc_list;
int num;
sa_family_t lifc_family;
int lifc_flags = LIFC_NOXMIT;
int syserr;
int i;
/*
* Validate the protocol family.
*/
if ((proto != AF_UNSPEC) &&
(proto != AF_INET) &&
(proto != AF_INET6)) {
errno = EINVAL;
return (ICFG_BAD_PROTOCOL);
}
lifc_family = proto;
/*
* Open a socket. Note that the AF_INET domain seems to
* support both IPv4 and IPv6.
*/
if ((sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0) {
return (ICFG_FAILURE);
}
/*
* Get the number of interfaces and allocate a buffer
* large enough to allow the interfaces to be enumerated.
*/
lifn.lifn_family = lifc_family;
lifn.lifn_flags = lifc_flags;
if (ioctl(sock, SIOCGLIFNUM, (char *)&lifn) < 0) {
syserr = errno;
(void) close(sock);
errno = syserr;
return (ICFG_FAILURE);
}
num = lifn.lifn_count;
bufsize = num * sizeof (struct lifreq);
buf = malloc(bufsize);
if (buf == NULL) {
syserr = errno;
(void) close(sock);
errno = syserr;
return (ICFG_NO_MEMORY);
}
/*
* Obtain a list of the interfaces.
*/
lifc.lifc_family = lifc_family;
lifc.lifc_flags = lifc_flags;
lifc.lifc_len = bufsize;
lifc.lifc_buf = buf;
if (ioctl(sock, SIOCGLIFCONF, (char *)&lifc) < 0) {
syserr = errno;
(void) close(sock);
free(buf);
errno = syserr;
return (ICFG_FAILURE);
}
(void) close(sock);
bufsize = num * sizeof (icfg_if_t);
loc_list = malloc(bufsize);
if (loc_list == NULL) {
syserr = errno;
free(buf);
errno = syserr;
return (ICFG_NO_MEMORY);
}
lifrp = lifc.lifc_req;
for (i = 0; i < num; i++, lifrp++) {
(void) strlcpy(loc_list[i].if_name, lifrp->lifr_name,
sizeof (loc_list[i].if_name));
if (lifrp->lifr_addr.ss_family == AF_INET) {
loc_list[i].if_protocol = AF_INET;
} else {
loc_list[i].if_protocol = AF_INET6;
}
}
*list = loc_list;
*numif = num;
free(buf);
return (ICFG_SUCCESS);
}
typedef struct linklist {
struct linklist *ll_next;
char ll_name[DLPI_LINKNAME_MAX];
} linklist_t;
typedef struct linkwalk {
linklist_t *lw_list;
int lw_num;
int lw_err;
} linkwalk_t;
static boolean_t
add_link_list(const char *link, void *arg)
{
linkwalk_t *lwp = (linkwalk_t *)arg;
linklist_t *entry = NULL;
if ((entry = calloc(1, sizeof (linklist_t))) == NULL) {
lwp->lw_err = ENOMEM;
return (B_TRUE);
}
(void) strlcpy(entry->ll_name, link, DLPI_LINKNAME_MAX);
if (lwp->lw_list == NULL)
lwp->lw_list = entry;
else
lwp->lw_list->ll_next = entry;
lwp->lw_num++;
return (B_FALSE);
}
/*
* Returns a list of data links that can be plumbed. The list of interfaces is
* returned as an array of icfg_if_t structures. The number of interfaces in
* the array will be returned via the 'numif' argument.
*
* Returns: ICFG_SUCCESS, ICFG_NO_MEMORY or ICFG_FAILURE.
*/
static int
get_link_list(icfg_if_t **listp, int *numif) {
linkwalk_t lw = {NULL, 0, 0};
linklist_t *entry, *next;
icfg_if_t *list;
int save_errno = 0;
int ret = ICFG_FAILURE;
dlpi_walk(add_link_list, &lw, 0);
if (lw.lw_err != 0) {
errno = lw.lw_err;
goto done;
}
if (lw.lw_num == 0) {
/* no links found, nothing else to do */
*listp = NULL;
*numif = 0;
return (ICFG_SUCCESS);
}
list = calloc(lw.lw_num, sizeof (icfg_if_t));
if (list == NULL) {
ret = ICFG_NO_MEMORY;
goto done;
}
*listp = list;
for (entry = lw.lw_list; entry != NULL; entry = entry->ll_next) {
(void) strlcpy(list->if_name, entry->ll_name,
sizeof (list->if_name));
list->if_protocol = AF_UNSPEC;
list++;
}
*numif = lw.lw_num;
ret = ICFG_SUCCESS;
done:
save_errno = errno;
for (entry = lw.lw_list; entry != NULL; entry = next) {
next = entry->ll_next;
free(entry);
}
errno = save_errno;
return (ret);
}
/*
* Returns a list of network interfaces. The list of
* interfaces is returned as an array of icfg_if_t structures.
* The number of interfaces in the array will be returned via
* the 'numif' argument. Since the array of interfaces is
* allocated by this API, the caller is responsible for freeing
* the memory associated with this array by calling
* icfg_free_if_list().
*
* The 'proto' argument is used by the caller to define which
* interfaces are to be listed by the API. The possible values
* for 'proto' are AF_INET, AF_INET6, and AF_UNSPEC.
*
* The 'type' argument is used by the caller specify whether
* to enumerate installed network interfaces or plumbed
* network interfaces. The value for 'type' can be ICFG_PLUMBED
* or ICFG_INSTALLED.
*/
int
icfg_get_if_list(icfg_if_t **list, int *numif, int proto, int type)
{
if (list == NULL || numif == NULL)
return (ICFG_INVALID_ARG);
*list = NULL;
*numif = 0;
if (type == ICFG_PLUMBED) {
return (get_plumbed_if_list(list, numif, proto));
} else if (type == ICFG_INSTALLED) {
return (get_link_list(list, numif));
} else {
errno = EINVAL;
return (ICFG_FAILURE);
}
}
/*
* Frees the memory allocated by icfg_get_list().
*/
void
icfg_free_if_list(icfg_if_t *list)
{
free(list);
list = NULL;
}
/*
* Determines whether or not an interface name represents
* a logical interface or not.
*
* Returns: B_TRUE if logical, B_FALSE if not.
*
* Note: this API can be vastly improved once interface naming
* is resolved in the future. This will do for now.
*/
boolean_t
icfg_is_logical(icfg_handle_t handle)
{
return (strchr(icfg_if_name(handle), ICFG_LOGICAL_SEP)
!= NULL);
}
/*
* Determines whether or not an interface name represents a loopback
* interface or not.
*
* Returns: B_TRUE if loopback, B_FALSE if not.
*/
static boolean_t
icfg_is_loopback(icfg_handle_t handle)
{
return (strcmp(icfg_if_name(handle), LOOPBACK_IF) == 0);
}
/*
* Given a sockaddr representation of an IPv4 or IPv6 address returns the
* string representation. Note that 'sockaddr' should point at the correct
* sockaddr structure for the address family (sockaddr_in for AF_INET or
* sockaddr_in6 for AF_INET6) or alternatively at a sockaddr_storage
* structure.
*
* Returns: ICFG_SUCCESS or ICFG_FAILURE.
*/
int
icfg_sockaddr_to_str(sa_family_t af, const struct sockaddr *sockaddr,
char *straddr, size_t len)
{
const void *addr = sockaddr;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
const char *str;
int ret = ICFG_FAILURE;
if (af == AF_INET) {
sin = (struct sockaddr_in *)addr;
str = inet_ntop(AF_INET, (void *)&sin->sin_addr, straddr, len);
} else if (af == AF_INET6) {
sin6 = (struct sockaddr_in6 *)addr;
str = inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, straddr,
len);
} else {
errno = EINVAL;
return (ICFG_FAILURE);
}
if (str != NULL) {
ret = ICFG_SUCCESS;
}
return (ret);
}
/*
* Given a string representation of an IPv4 or IPv6 address returns the
* sockaddr representation. Note that 'sockaddr' should point at the correct
* sockaddr structure for the address family (sockaddr_in for AF_INET or
* sockaddr_in6 for AF_INET6) or alternatively at a sockaddr_storage
* structure.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_ADDR or ICFG_FAILURE.
*/
int
icfg_str_to_sockaddr(sa_family_t af, const char *straddr,
struct sockaddr *sockaddr, socklen_t *addrlen)
{
void *addr = sockaddr;
struct sockaddr_in *sin;
struct sockaddr_in6 *sin6;
int ret;
int err;
if (af == AF_INET) {
if (*addrlen < sizeof (*sin)) {
errno = ENOSPC;
return (ICFG_FAILURE);
}
*addrlen = sizeof (*sin);
sin = (struct sockaddr_in *)addr;
sin->sin_family = AF_INET;
err = inet_pton(AF_INET, straddr, &sin->sin_addr);
} else if (af == AF_INET6) {
if (*addrlen < sizeof (*sin6)) {
errno = ENOSPC;
return (ICFG_FAILURE);
}
*addrlen = sizeof (*sin6);
sin6 = (struct sockaddr_in6 *)addr;
sin6->sin6_family = AF_INET6;
err = inet_pton(AF_INET6, straddr, &sin6->sin6_addr);
} else {
errno = EINVAL;
return (ICFG_FAILURE);
}
if (err == 0) {
ret = ICFG_BAD_ADDR;
} else if (err == 1) {
ret = ICFG_SUCCESS;
} else {
ret = ICFG_FAILURE;
}
return (ret);
}
/*
* Adds the IP address contained in the 'addr' argument to the physical
* interface represented by the handle passed in. At present,
* additional IP addresses assigned to a physical interface are
* represented as logical interfaces.
*
* If the 'handle' argument is a handle to a physical interface, a logical
* interface will be created, named by the next unused logical unit number
* for that physical interface.
*
* If the 'handle' argument is a handle to an logical interface, then that
* logical interface is the one that will be created. If the logical
* interface model is abandoned in the future, passing in a logical
* interface name should result in ICFG_UNSUPPORTED being returned.
*
* If the 'new_handle' argument is not NULL, then a handle is created for the
* new IP address alias and returned to the caller via 'new_handle'.
* At present this handle refers to a logical interface, but in the future
* it may represent an IP address alias, and be used for setting/retrieving
* address-related information only.
*
*
* Returns: ICFG_SUCCESS, ICFG_BAD_ADDR, ICFG_DAD_FOUND, ICFG_EXISTS
* or ICFG_FAILURE.
*/
int
icfg_add_addr(icfg_handle_t handle, icfg_handle_t *new_handle,
const struct sockaddr *addr, socklen_t addrlen)
{
struct lifreq lifr;
size_t addrsize;
int ret = ICFG_SUCCESS;
icfg_handle_t loc_handle;
if (addr->sa_family != icfg_if_protocol(handle))
return (ICFG_BAD_ADDR);
switch (addr->sa_family) {
case AF_INET:
addrsize = sizeof (struct sockaddr_in);
break;
case AF_INET6:
addrsize = sizeof (struct sockaddr_in6);
break;
default:
return (ICFG_BAD_ADDR);
}
if (addrlen < addrsize) {
errno = ENOSPC;
return (ICFG_FAILURE);
}
/*
* See comments in ifconfig.c as to why this dance is necessary.
*/
(void) memset(&lifr, 0, sizeof (lifr));
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
if (ioctl(handle->ifh_sock, SIOCLIFADDIF, (caddr_t)&lifr) < 0) {
if (errno == EEXIST)
return (ICFG_EXISTS);
else
return (ICFG_FAILURE);
}
/* Create the handle for the new interface name. */
ret = icfg_open(&loc_handle, &(handle->ifh_interface));
if (ret != ICFG_SUCCESS) {
return (ret);
}
(void) strlcpy(loc_handle->ifh_interface.if_name,
lifr.lifr_name,
sizeof (loc_handle->ifh_interface.if_name));
if (addr != NULL)
ret = icfg_set_addr(loc_handle, addr, addrsize);
if (new_handle != NULL)
*new_handle = loc_handle;
else
icfg_close(loc_handle);
return (ret);
}
/*
* Removes specified IP address alias from physical interface. If the
* If the 'handle' argument is a handle to a physical interface, then
* the address alias removed must be specified by 'addr'. If the
* 'handle' argument is a handle for an IP address alias (currently
* represented as a logical interface), then that address alias
* (logical interface) is removed and the 'addr' argument is ignored.
*
* Under the logical interface model, an interface may only be removed
* if the interface is 'down'.
*
* Returns: ICFG_SUCCESS, ICFG_BAD_ADDR, ICFG_IF_UP, ICFG_NO_EXIST,
* or ICFG_FAILURE.
*/
int
icfg_remove_addr(icfg_handle_t handle, const struct sockaddr *addr,
socklen_t addrlen)
{
struct lifreq lifr;
size_t addrsize;
switch (icfg_if_protocol(handle)) {
case AF_INET:
addrsize = sizeof (struct sockaddr_in);
break;
case AF_INET6:
addrsize = sizeof (struct sockaddr_in6);
break;
default:
return (ICFG_BAD_ADDR);
}
if (addr != NULL) {
if (addrlen < addrsize) {
errno = ENOSPC;
return (ICFG_FAILURE);
}
(void) memcpy(&lifr.lifr_addr, addr, addrsize);
} else {
(void) memset(&lifr.lifr_addr, 0, sizeof (lifr.lifr_addr));
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
if (ioctl(handle->ifh_sock, SIOCLIFREMOVEIF, (caddr_t)&lifr) < 0)
return (ICFG_FAILURE);
return (ICFG_SUCCESS);
}
/*
* Wrapper for sending a nontransparent I_STR ioctl().
* Returns: Result from ioctl().
*
* Same as in usr/src/cmd/cmd-inet/usr.sbin/ifconfig/ifconfig.c
*/
static int
strioctl(int s, int cmd, char *buf, int buflen)
{
struct strioctl ioc;
(void) memset(&ioc, 0, sizeof (ioc));
ioc.ic_cmd = cmd;
ioc.ic_timout = 0;
ioc.ic_len = buflen;
ioc.ic_dp = buf;
return (ioctl(s, I_STR, (char *)&ioc));
}
/*
* Open a stream on /dev/udp{,6}, 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.
*
* Same as in usr/src/cmd/cmd-inet/usr.sbin/ifconfig/ifconfig.c
*/
static int
open_arp_on_udp(char *udp_dev_name)
{
int fd;
if ((fd = open(udp_dev_name, O_RDWR)) == -1)
return (-1);
errno = 0;
while (ioctl(fd, I_POP, 0) != -1)
;
if (errno == EINVAL && ioctl(fd, I_PUSH, ARP_MOD_NAME) != -1)
return (fd);
(void) close(fd);
return (-1);
}
/*
* We need to plink both the arp-device stream and the arp-ip-device stream.
* However the muxid is stored only in IP. Plumbing 2 streams individually
* is not atomic, and if ifconfig is killed, the resulting plumbing can
* be inconsistent. For eg. if only the arp stream is plumbed, we have lost
* the muxid, and the half-baked plumbing can neither be unplumbed nor
* replumbed, thus requiring a reboot. To avoid the above the following
* scheme is used.
*
* We ask IP to enforce atomicity of plumbing the arp and IP streams.
* This is done by pushing arp on to the mux (/dev/udp). ARP adds some
* extra information in the I_PLINK and I_PUNLINK ioctls to let IP know
* that the plumbing/unplumbing has to be done atomically. Ifconfig plumbs
* the IP stream first, and unplumbs it last. The kernel (IP) does not
* allow IP stream to be unplumbed without unplumbing arp stream. Similarly
* it does not allow arp stream to be plumbed before IP stream is plumbed.
* There is no need to use SIOCSLIFMUXID, since the whole operation is atomic,
* and IP uses the info in the I_PLINK message to get the muxid.
*
* a. STREAMS does not allow us to use /dev/ip itself as the mux. So we use
* /dev/udp{,6}.
* b. SIOCGLIFMUXID returns the muxid corresponding to the V4 or V6 stream
* depending on the open i.e. V4 vs V6 open. So we need to use /dev/udp
* or /dev/udp6 for SIOCGLIFMUXID and SIOCSLIFMUXID.
* c. We need to push ARP in order to get the required kernel support for
* atomic plumbings. The actual work done by ARP is explained in arp.c
* Without pushing ARP, we will still be able to plumb/unplumb. But
* it is not atomic, and is supported by the kernel for backward
* compatibility for other utilities like atmifconfig etc. In this case
* the utility must use SIOCSLIFMUXID.
*
* Returns: ICFG_SUCCESS, ICFG_EXISTS, ICFG_BAD_ADDR, ICFG_FAILURE,
* ICFG_DLPI_*, ICFG_NO_PLUMB_IP, ICFG_NO_PLUMB_ARP,
* ICFG_NO_UNPLUMB_ARP
*/
int
icfg_plumb(icfg_handle_t handle)
{
int ip_muxid;
int mux_fd, ip_fd, arp_fd;
uint_t ppa;
char *udp_dev_name;
char provider[DLPI_LINKNAME_MAX];
dlpi_handle_t dh_arp, dh_ip;
struct lifreq lifr;
int dlpi_ret, ret = ICFG_SUCCESS;
int saved_errno; /* to set errno after close() */
int dh_arp_ret; /* to track if dh_arp was successfully opened */
zoneid_t zoneid;
/* Logical and loopback interfaces are just added */
if (icfg_is_loopback(handle) || icfg_is_logical(handle))
return (icfg_add_addr(handle, NULL, NULL, 0));
/*
* If we're running in the global zone, we need to
* make sure this link is actually assigned to us.
*
* This is not an issue if we are not in the global
* zone, as we simply can't see links we don't own.
*/
zoneid = getzoneid();
if (zoneid == GLOBAL_ZONEID) {
dladm_handle_t dlh;
dladm_status_t status;
datalink_id_t linkid;
if (dladm_open(&dlh) != DLADM_STATUS_OK)
return (ICFG_FAILURE);
status = dladm_name2info(dlh, icfg_if_name(handle), &linkid,
NULL, NULL, NULL);
dladm_close(dlh);
if (status != DLADM_STATUS_OK)
return (ICFG_INVALID_ARG);
zoneid = ALL_ZONES;
if (zone_check_datalink(&zoneid, linkid) == 0)
return (ICFG_INVALID_ARG);
}
/*
* We use DLPI_NOATTACH because the ip module will do the attach
* itself for DLPI style-2 devices.
*/
if ((dlpi_ret = dlpi_open(icfg_if_name(handle), &dh_ip,
DLPI_NOATTACH)) != DLPI_SUCCESS) {
return (dlpi_error_to_icfg_error(dlpi_ret));
}
if ((dlpi_ret = dlpi_parselink(icfg_if_name(handle), provider,
&ppa)) != DLPI_SUCCESS) {
ret = dlpi_error_to_icfg_error(dlpi_ret);
goto done;
}
ip_fd = dlpi_fd(dh_ip);
if (ioctl(ip_fd, I_PUSH, IP_MOD_NAME) == -1) {
ret = ICFG_NO_PLUMB_IP;
goto done;
}
/*
* Push the ARP module onto the interface stream. IP uses
* this to send resolution requests up to ARP. We need to
* do this before the SLIFNAME ioctl is sent down because
* the interface becomes publicly known as soon as the SLIFNAME
* ioctl completes. Thus some other process trying to bring up
* the interface after SLIFNAME but before we have pushed ARP
* could hang. We pop the module again later if it is not needed.
*/
if (ioctl(ip_fd, I_PUSH, ARP_MOD_NAME) == -1) {
ret = ICFG_NO_PLUMB_ARP;
goto done;
}
/*
* Set appropriate IFF flags. The kernel only allows us to
* modify IFF_IPv[46], IFF_BROADCAST, and IFF_XRESOLV in the
* SIOCSLIFNAME ioctl call; so we only need to set the ones
* from that set that we care about.
*/
if (icfg_if_protocol(handle) == AF_INET6)
lifr.lifr_flags = IFF_IPV6;
else
lifr.lifr_flags = IFF_IPV4 | IFF_BROADCAST;
/* record the device and module names as interface name */
lifr.lifr_ppa = ppa;
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
/* set the interface name */
if (ioctl(ip_fd, SIOCSLIFNAME, (char *)&lifr) == -1) {
if (errno == EALREADY)
ret = ICFG_EXISTS;
else
ret = ICFG_NO_PLUMB_IP;
goto done;
}
/* Get the full set of existing flags for this stream */
if (ioctl(ip_fd, SIOCGLIFFLAGS, (char *)&lifr) == -1) {
if (errno == ENXIO)
ret = ICFG_NO_EXIST;
else
ret = ICFG_FAILURE;
goto done;
}
/* Check if arp is not actually needed */
if (lifr.lifr_flags & (IFF_NOARP|IFF_IPV6)) {
if (ioctl(ip_fd, I_POP, 0) == -1) {
ret = ICFG_NO_UNPLUMB_ARP;
goto done;
}
}
/*
* Open "/dev/udp" 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.
*/
if (icfg_if_protocol(handle) == AF_INET6)
udp_dev_name = UDP6_DEV_NAME;
else
udp_dev_name = UDP_DEV_NAME;
if ((mux_fd = open_arp_on_udp(udp_dev_name)) == -1) {
ret = ICFG_NO_PLUMB_ARP;
goto done;
}
/* Check if arp is not needed */
if (lifr.lifr_flags & (IFF_NOARP|IFF_IPV6)) {
/*
* PLINK the interface stream so that ifconfig can exit
* without tearing down the stream.
*/
if ((ip_muxid = ioctl(mux_fd, I_PLINK, ip_fd)) == -1) {
ret = ICFG_NO_PLUMB_IP;
goto done;
}
(void) close(mux_fd);
dlpi_close(dh_ip);
return (ICFG_SUCCESS);
}
/*
* This interface does use ARP, so set up a separate stream
* from the interface to ARP.
*
* Note: modules specified by the user are pushed
* only on the interface stream, not on the ARP stream.
*
* We use DLPI_NOATTACH because the arp module will do the attach
* itself for DLPI style-2 devices.
*/
if ((dh_arp_ret = dlpi_open(icfg_if_name(handle), &dh_arp,
DLPI_NOATTACH)) != DLPI_SUCCESS) {
ret = dlpi_error_to_icfg_error(dh_arp_ret);
goto done;
}
arp_fd = dlpi_fd(dh_arp);
if (ioctl(arp_fd, I_PUSH, ARP_MOD_NAME) == -1) {
ret = ICFG_NO_PLUMB_ARP;
goto done;
}
/*
* Tell ARP the name and unit number for this interface.
* Note that arp has no support for transparent ioctls.
*/
if (strioctl(arp_fd, SIOCSLIFNAME, (char *)&lifr,
sizeof (lifr)) == -1) {
ret = ICFG_NO_PLUMB_ARP;
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) {
ret = ICFG_NO_PLUMB_IP;
goto done;
}
if (ioctl(mux_fd, I_PLINK, arp_fd) == -1) {
(void) ioctl(mux_fd, I_PUNLINK, ip_muxid);
ret = ICFG_NO_PLUMB_ARP;
}
done:
/* dlpi_close() may change errno, so save it */
saved_errno = errno;
dlpi_close(dh_ip);
if (dh_arp_ret == DLPI_SUCCESS)
dlpi_close(dh_arp);
if (mux_fd != -1)
(void) close(mux_fd);
if (ret != ICFG_SUCCESS)
errno = saved_errno;
return (ret);
}
static boolean_t
ifaddr_down(ifaddrlistx_t *ifaddrp)
{
icfg_handle_t addrh;
icfg_if_t addrif;
uint64_t addrflags;
boolean_t ret;
addrif.if_protocol = ifaddrp->ia_flags & IFF_IPV6 ? AF_INET6 : AF_INET;
(void) strlcpy(addrif.if_name, ifaddrp->ia_name,
sizeof (addrif.if_name));
if (icfg_open(&addrh, &addrif) != ICFG_SUCCESS)
return (B_FALSE);
if (icfg_get_flags(addrh, &addrflags) != ICFG_SUCCESS)
return (B_FALSE);
addrflags &= ~IFF_UP;
if (icfg_set_flags(addrh, addrflags) != ICFG_SUCCESS) {
ret = B_FALSE;
goto done;
}
/*
* Make sure that DAD activity (observable by IFF_DUPLICATE)
* has also been stopped. If we were successful in downing
* the address, the get_flags will fail, as the addr will no
* longer exist.
*/
if ((icfg_get_flags(addrh, &addrflags) == ICFG_SUCCESS) &&
addrflags & IFF_DUPLICATE) {
struct sockaddr_storage ss;
socklen_t alen = sizeof (ss);
int plen;
/*
* getting/setting the address resets DAD; and since
* we've already turned off IFF_UP, DAD will remain
* disabled.
*/
if ((icfg_get_addr(addrh, (struct sockaddr *)&ss, &alen, &plen,
B_FALSE) != ICFG_SUCCESS) ||
(icfg_set_addr(addrh, (struct sockaddr *)&ss, alen)
!= ICFG_SUCCESS)) {
ret = B_FALSE;
goto done;
}
}
ret = B_TRUE;
done:
icfg_close(addrh);
return (ret);
}
/*
* If this is a physical interface then remove it.
* If it is a logical interface name use SIOCLIFREMOVEIF to
* remove it. In both cases fail if it doesn't exist.
*
* Returns: ICFG_SUCCESS, ICFG_EXISTS, ICFG_NO_EXIST, ICFG_BAD_ADDR,
* ICFG_FAILURE, ICFG_NO_UNPLUMB_IP, ICFG_NO_UNPLUMB_ARP,
* ICFG_INVALID_ARG, ICFG_NO_IP_MUX
*
* Same as inetunplumb() in usr/src/cmd/cmd-inet/usr.sbin/ifconfig/ifconfig.c
*/
int
icfg_unplumb(icfg_handle_t handle)
{
int ip_muxid, arp_muxid;
int mux_fd;
int muxid_fd;
char *udp_dev_name;
uint64_t flags;
boolean_t changed_arp_muxid = B_FALSE;
int save_errno;
struct lifreq lifr;
int ret = ICFG_SUCCESS;
boolean_t v6 = (icfg_if_protocol(handle) == AF_INET6);
/* Make sure interface exists to start with */
if ((ret = icfg_get_flags(handle, &flags)) != ICFG_SUCCESS) {
return (ret);
}
if (icfg_is_loopback(handle) || icfg_is_logical(handle)) {
char *strptr = strchr(icfg_if_name(handle), ICFG_LOGICAL_SEP);
/* Can't unplumb logical interface zero */
if (strptr != NULL && strcmp(strptr, ":0") == 0)
return (ICFG_INVALID_ARG);
return (icfg_remove_addr(handle, NULL, 0));
}
/*
* 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;
else
udp_dev_name = UDP_DEV_NAME;
if ((muxid_fd = open(udp_dev_name, O_RDWR)) == -1)
return (ICFG_NO_UNPLUMB_ARP);
if ((mux_fd = open_arp_on_udp(udp_dev_name)) == -1) {
ret = ICFG_NO_UNPLUMB_ARP;
goto done;
}
(void) strlcpy(lifr.lifr_name, icfg_if_name(handle),
sizeof (lifr.lifr_name));
if (ioctl(muxid_fd, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) {
ret = ICFG_FAILURE;
goto done;
}
flags = lifr.lifr_flags;
/*
* libinetcfg's only current consumer is nwamd; and we expect it to
* be replaced before any other consumers come along. NWAM does not
* currently support creation of IPMP groups, so icfg_plumb(), for
* example, does not do any IPMP-specific handling. However, it's
* possible nwamd might need to unplumb an IPMP group, so we include
* IPMP group handling here.
*/
again:
if (flags & IFF_IPMP) {
lifgroupinfo_t lifgr;
ifaddrlistx_t *ifaddrs, *ifaddrp;
/*
* 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.
*/
ret = icfg_get_groupname(handle, lifgr.gi_grname, LIFGRNAMSIZ);
if (ret != ICFG_SUCCESS)
return (ret);
ret = icfg_get_groupinfo(handle, &lifgr);
if (ret != ICFG_SUCCESS)
return (ret);
/* make sure the group is empty */
if ((v6 && lifgr.gi_nv6 != 0) || (!v6 && lifgr.gi_nv4 != 0))
return (ICFG_INVALID_ARG);
/*
* The kernel will fail the I_PUNLINK if the IPMP interface
* has administratively up addresses; bring 'em down.
*/
if (ifaddrlistx(icfg_if_name(handle), IFF_UP|IFF_DUPLICATE,
0, &ifaddrs) == -1)
return (ICFG_FAILURE);
ifaddrp = ifaddrs;
for (; ifaddrp != NULL; ifaddrp = ifaddrp->ia_next) {
if (((ifaddrp->ia_flags & IFF_IPV6) && !v6) ||
(!(ifaddrp->ia_flags && IFF_IPV6) && v6))
continue;
if (!ifaddr_down(ifaddrp)) {
ifaddrlistx_free(ifaddrs);
return (ICFG_FAILURE);
}
}
ifaddrlistx_free(ifaddrs);
}
if (ioctl(muxid_fd, SIOCGLIFMUXID, (caddr_t)&lifr) < 0) {
ret = ICFG_NO_IP_MUX;
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 icfg_get_groupname() 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;
} else {
ret = ICFG_NO_UNPLUMB_ARP;
}
}
}
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 icfg_get_groupname() call.
*/
if (errno == EBUSY && (flags && IFF_IPMP))
goto again;
ret = ICFG_NO_UNPLUMB_IP;
}
done:
/* close() may change errno, so save it */
save_errno = errno;
(void) close(muxid_fd);
if (mux_fd != -1)
(void) close(mux_fd);
if (ret != ICFG_SUCCESS)
errno = save_errno;
return (ret);
}