diff options
author | Girish Moodalbail <Girish.Moodalbail@Sun.COM> | 2010-03-26 17:53:11 -0400 |
---|---|---|
committer | Girish Moodalbail <Girish.Moodalbail@Sun.COM> | 2010-03-26 17:53:11 -0400 |
commit | 6e91bba0d6c6bdabbba62cefae583715a4a58e2a (patch) | |
tree | e10bc428e6a27ac87b541b72769d8095d64e894f /usr/src/lib | |
parent | 00a57bdfe7eeb62d10d0c0b3aab64d24a4d89287 (diff) | |
download | illumos-gate-6e91bba0d6c6bdabbba62cefae583715a4a58e2a.tar.gz |
PSARC 2009/306 Brussels II - ipadm and libipadm
PSARC 2010/080 Brussels II addendum
6827318 Brussels Phase II aka ipadm(1m)
6731945 need BSD getifaddrs() API
6909065 explicitly disallow non-contiguous netmasks in the next minor release
6853922 ifconfig dumps core when ether address is non-hexadecimal.
6815806 ipReasmTimeout value should be variable
6567083 nd_getset has some dead and confusing code.
6884466 remove unused tcp/sctp ndd tunables
6928813 Comments at odds with default value of tcp_time_wait_interval
6236982 ifconfig usesrc lets adapter use itself as source address
6936855 modifying the ip6_strict_src_multihoming to non-zero value will unbind V4 IREs
Diffstat (limited to 'usr/src/lib')
35 files changed, 10652 insertions, 88 deletions
diff --git a/usr/src/lib/Makefile b/usr/src/lib/Makefile index 4086c4210c..2ed5c76543 100644 --- a/usr/src/lib/Makefile +++ b/usr/src/lib/Makefile @@ -112,6 +112,7 @@ SUBDIRS += \ libcryptoutil \ libinetcfg \ libinetutil \ + libipadm \ libipmp \ libiscsit \ libkmf \ @@ -334,6 +335,7 @@ MSGSUBDIRS= \ libilb \ libinetutil \ libinstzones \ + libipadm \ libnsl \ libnwam \ libpam \ @@ -386,6 +388,7 @@ HDRSUBDIRS= \ libc \ libcmd \ libcmdutils \ + libcommputil \ libcontract \ libcpc \ libctf \ @@ -411,8 +414,7 @@ HDRSUBDIRS= \ libfru \ libfstyp \ libgen \ - libwanboot \ - libwanbootutil \ + libipadm \ libipsecutil \ libinetcfg \ libinetsvc \ @@ -445,7 +447,6 @@ HDRSUBDIRS= \ librdc \ libscf \ libsip \ - libcommputil \ libsmbios \ librestart \ librpcsvc \ @@ -456,6 +457,7 @@ HDRSUBDIRS= \ libshell \ libslp \ libsmedia \ + libsocket \ libsqlite \ libfcoe \ libstmf \ @@ -473,6 +475,8 @@ HDRSUBDIRS= \ libumem \ libunistat \ libuutil \ + libwanboot \ + libwanbootutil \ libwrap \ libxcurses2 \ libzfs \ @@ -592,7 +596,9 @@ libefi: libuuid libfstyp: libnvpair libelfsign: libcryptoutil libkmf libidmap: libadutils libldap5 libavl libsldap -libinetcfg: libnsl libsocket libdlpi +libinetcfg: libnsl libsocket libdlpi libinetutil +libipadm: libnsl libinetutil libsocket libdlpi libnvpair libdhcpagent \ + libdladm libsecdb libiscsit: libc libnvpair libstmf libuuid libnsl libkmf: libcryptoutil pkcs11 libnsl: libmd5 libscf diff --git a/usr/src/lib/brand/solaris10/zone/s10_boot.ksh b/usr/src/lib/brand/solaris10/zone/s10_boot.ksh index 09c4f39a59..5a57696eef 100644 --- a/usr/src/lib/brand/solaris10/zone/s10_boot.ksh +++ b/usr/src/lib/brand/solaris10/zone/s10_boot.ksh @@ -20,7 +20,7 @@ # CDDL HEADER END # # -# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # # s10 boot script. @@ -144,6 +144,13 @@ safe_dir /sbin replace_with_native /sbin/ifconfig 0555 root:bin # +# PSARC 2009/306 removed the ND_SET/ND_GET ioctl's for modifying +# IP/TCP/UDP/SCTP/ICMP tunables. If S10 ndd(1M) is used within an +# S10 container, the kernel will return EINVAL. So we need this. +# +replace_with_native /usr/sbin/ndd 0555 root:bin + +# # Replace automount and automountd with native wrappers. # if [ ! -h $ZONEROOT/usr/lib/fs/autofs -a -d $ZONEROOT/usr/lib/fs/autofs ]; then diff --git a/usr/src/lib/libbc/inc/include/net/if.h b/usr/src/lib/libbc/inc/include/net/if.h index 389bb290b6..513fa0d2bf 100644 --- a/usr/src/lib/libbc/inc/include/net/if.h +++ b/usr/src/lib/libbc/inc/include/net/if.h @@ -1,5 +1,5 @@ /* - * Copyright 1990 Sun Microsystems, Inc. All rights reserved. + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -12,8 +12,6 @@ #ifndef _net_if_h #define _net_if_h -#pragma ident "%Z%%M% %I% %E% SMI" - /* * Structures defining a network interface, providing a packet * transport mechanism (ala level 0 of the PUP protocols). @@ -94,7 +92,7 @@ struct ifnet { #define IFF_NOARP 0x80 /* no address resolution protocol */ #define IFF_PROMISC 0x100 /* receive all packets */ #define IFF_ALLMULTI 0x200 /* receive all multicast packets */ -#define IFF_PRIVATE 0x8000 /* do not advertise */ +#define IFF_PRIVATE 0x8000 /* do not advertise */ /* flags set internally only: */ #define IFF_CANTCHANGE \ @@ -131,8 +129,8 @@ struct ifnet { * IF_ADJ should be used otherwise to adjust for its presence. */ #define IF_ADJ(m) { \ - (m)->m_off += sizeof(struct ifnet *); \ - (m)->m_len -= sizeof(struct ifnet *); \ + (m)->m_off += sizeof (struct ifnet *); \ + (m)->m_len -= sizeof (struct ifnet *); \ if ((m)->m_len == 0) { \ struct mbuf *n; \ MFREE((m), n); \ @@ -175,8 +173,12 @@ struct ifaddr { struct sockaddr ifu_broadaddr; struct sockaddr ifu_dstaddr; } ifa_ifu; +#ifndef ifa_broadaddr #define ifa_broadaddr ifa_ifu.ifu_broadaddr /* broadcast address */ +#endif +#ifndef ifa_dstaddr #define ifa_dstaddr ifa_ifu.ifu_dstaddr /* other end of p-to-p link */ +#endif struct ifnet *ifa_ifp; /* back-pointer to interface */ struct ifaddr *ifa_next; /* next address for interface */ }; @@ -223,7 +225,7 @@ struct ifreq { struct ifr_fddi_gen_struct { int ifru_fddi_gioctl; /* field for gen ioctl */ - caddr_t ifru_fddi_gaddr ; /* Generic ptr to a field */ + caddr_t ifru_fddi_gaddr; /* Generic ptr to a field */ } ifru_fddi_gstruct; } ifr_ifru; @@ -237,10 +239,10 @@ struct ifreq { #define ifr_data ifr_ifru.ifru_data /* for use by interface */ /* FDDI specific */ -#define ifr_dnld_req ifr_ifru.ifru_dnld_req -#define ifr_fddi_stat ifr_ifru.ifru_fddi_stat -#define ifr_fddi_netmap ifr_ifru.ifru_netmapent /* FDDI network map entries */ -#define ifr_fddi_gstruct ifr_ifru.ifru_fddi_gstruct +#define ifr_dnld_req ifr_ifru.ifru_dnld_req +#define ifr_fddi_stat ifr_ifru.ifru_fddi_stat +#define ifr_fddi_netmap ifr_ifru.ifru_netmapent /* FDDI network map entries */ +#define ifr_fddi_gstruct ifr_ifru.ifru_fddi_gstruct }; diff --git a/usr/src/lib/libinetcfg/common/inetcfg.c b/usr/src/lib/libinetcfg/common/inetcfg.c index cdcbf00329..f61df0e287 100644 --- a/usr/src/lib/libinetcfg/common/inetcfg.c +++ b/usr/src/lib/libinetcfg/common/inetcfg.c @@ -128,32 +128,6 @@ dlpi_error_to_icfg_error(int err) } /* - * 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 @@ -573,34 +547,16 @@ 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 (plen2mask(prefixlen, icfg_if_protocol(handle), + &lifr.lifr_addr) != 0) { + return (ICFG_FAILURE); } - if (ioctl(handle->ifh_sock, SIOCSLIFNETMASK, (caddr_t)&lifr) < 0) { + if (ioctl(handle->ifh_sock, SIOCSLIFNETMASK, (caddr_t)&lifr) < 0) return (ICFG_FAILURE); - } return (ICFG_SUCCESS); } diff --git a/usr/src/lib/libinetutil/common/inetutil.c b/usr/src/lib/libinetutil/common/inetutil.c index 195d080b79..be92c0e77d 100644 --- a/usr/src/lib/libinetutil/common/inetutil.c +++ b/usr/src/lib/libinetutil/common/inetutil.c @@ -20,15 +20,17 @@ */ /* - * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ #include <unistd.h> #include <netinet/in.h> #include <libinetutil.h> - -extern int getnetmaskbyaddr(const struct in_addr, struct in_addr *); +#include <inet/ip.h> +#include <strings.h> +#include <errno.h> +#include <libsocket_priv.h> /* * Internet utility functions. @@ -95,3 +97,97 @@ sockaddrcmp(const struct sockaddr_storage *ssp1, } return (B_FALSE); } + +/* + * Stores the netmask in `mask' for the given prefixlen `plen' and also sets + * `ss_family' in `mask'. + */ +int +plen2mask(uint_t prefixlen, sa_family_t af, struct sockaddr_storage *mask) +{ + uint8_t *addr; + + bzero(mask, sizeof (*mask)); + mask->ss_family = af; + if (af == AF_INET) { + if (prefixlen > IP_ABITS) + return (EINVAL); + addr = (uint8_t *)&((struct sockaddr_in *)mask)-> + sin_addr.s_addr; + } else { + if (prefixlen > IPV6_ABITS) + return (EINVAL); + addr = (uint8_t *)&((struct sockaddr_in6 *)mask)-> + sin6_addr.s6_addr; + } + + while (prefixlen > 0) { + if (prefixlen >= 8) { + *addr++ = 0xFF; + prefixlen -= 8; + continue; + } + *addr |= 1 << (8 - prefixlen); + prefixlen--; + } + return (0); +} + +/* + * Convert a mask to a prefix length. + * Returns prefix length on success, -1 otherwise. + */ +int +mask2plen(const struct sockaddr_storage *mask) +{ + int rc = 0; + uint8_t last; + uint8_t *addr; + int limit; + + if (mask->ss_family == AF_INET) { + limit = IP_ABITS; + addr = (uint8_t *)&((struct sockaddr_in *)mask)-> + sin_addr.s_addr; + } else { + limit = IPV6_ABITS; + addr = (uint8_t *)&((struct sockaddr_in6 *)mask)-> + sin6_addr.s6_addr; + } + + while (*addr == 0xff) { + rc += 8; + if (rc == limit) + return (limit); + addr++; + } + + last = *addr; + while (last != 0) { + rc++; + last = (last << 1) & 0xff; + } + + return (rc); +} + +/* + * Returns B_TRUE if the address in `ss' is INADDR_ANY for IPv4 or + * :: for IPv6. Otherwise, returns B_FALSE. + */ +boolean_t +sockaddrunspec(const struct sockaddr_storage *ss) +{ + struct sockaddr_storage zeroaddr = {0}; + + switch (ss->ss_family) { + case AF_INET: + return (((struct sockaddr_in *)ss)->sin_addr.s_addr == + INADDR_ANY); + case AF_INET6: + return (IN6_IS_ADDR_UNSPECIFIED( + &((struct sockaddr_in6 *)ss)->sin6_addr)); + } + + return (bcmp(&zeroaddr, ss, sizeof (zeroaddr)) == 0); +} diff --git a/usr/src/lib/libinetutil/common/libinetutil.h b/usr/src/lib/libinetutil/common/libinetutil.h index a285103af6..d294e6b50c 100644 --- a/usr/src/lib/libinetutil/common/libinetutil.h +++ b/usr/src/lib/libinetutil/common/libinetutil.h @@ -20,7 +20,7 @@ */ /* - * Copyright 2009 Sun Microsystems, Inc. All rights reserved. + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ @@ -50,10 +50,14 @@ typedef struct { char ifsp_devnm[LIFNAMSIZ]; /* only the device name */ } ifspec_t; -extern boolean_t ifparse_ifspec(const char *, ifspec_t *); -extern void get_netmask4(const struct in_addr *, struct in_addr *); -extern boolean_t sockaddrcmp(const struct sockaddr_storage *, - const struct sockaddr_storage *); +extern boolean_t ifparse_ifspec(const char *, ifspec_t *); +extern void get_netmask4(const struct in_addr *, struct in_addr *); +extern boolean_t sockaddrcmp(const struct sockaddr_storage *, + const struct sockaddr_storage *); +extern int plen2mask(uint_t, sa_family_t, + struct sockaddr_storage *); +extern int mask2plen(const struct sockaddr_storage *); +extern boolean_t sockaddrunspec(const struct sockaddr_storage *); /* * Extended version of the classic BSD ifaddrlist() interface: diff --git a/usr/src/lib/libinetutil/common/mapfile-vers b/usr/src/lib/libinetutil/common/mapfile-vers index 8bc79dd4cb..8dc7fb2c3a 100644 --- a/usr/src/lib/libinetutil/common/mapfile-vers +++ b/usr/src/lib/libinetutil/common/mapfile-vers @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # @@ -62,12 +62,15 @@ SUNWprivate_1.1 { iu_tq_destroy; iu_unregister_event; octet_to_hexascii; - sockaddrcmp; ofmt_open; ofmt_close; ofmt_print; ofmt_update_winsize; ofmt_strerror; + mask2plen; + plen2mask; + sockaddrcmp; + sockaddrunspec; local: *; }; diff --git a/usr/src/lib/libipadm/Makefile b/usr/src/lib/libipadm/Makefile new file mode 100644 index 0000000000..4e407d7fc4 --- /dev/null +++ b/usr/src/lib/libipadm/Makefile @@ -0,0 +1,61 @@ +# +# 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 $(SRC)/lib/Makefile.lib + +HDRS = libipadm.h ipadm_ndpd.h ipadm_ipmgmt.h +HDRDIR = common +SUBDIRS = $(MACH) +POFILE = libipadm.po +MSGFILES = common/libipadm.c common/ipadm_prop.c common/ipadm_persist.c \ + common/ipadm_addr.c common/ipadm_if.c common/ipadm_ndpd.c +XGETFLAGS = -a -x libipadm.xcl + +all := TARGET = all +clean := TARGET = clean +clobber := TARGET = clobber +install := TARGET = install +lint := TARGET = lint + +.KEEP_STATE: + +all clean clobber install lint: $(SUBDIRS) + +install_h: $(ROOTHDRS) + +check: $(CHECKHDRS) + +$(POFILE): pofile_MSGFILES + +_msg: $(MSGDOMAINPOFILE) + +$(SUBDIRS): FRC + @cd $@; pwd; $(MAKE) $(TARGET) + +FRC: + +include $(SRC)/Makefile.msg.targ +include $(SRC)/lib/Makefile.targ diff --git a/usr/src/lib/libipadm/Makefile.com b/usr/src/lib/libipadm/Makefile.com new file mode 100644 index 0000000000..60f677736e --- /dev/null +++ b/usr/src/lib/libipadm/Makefile.com @@ -0,0 +1,53 @@ +# +# 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. +# +# + +LIBRARY = libipadm.a +VERS = .1 +OBJECTS = libipadm.o ipadm_prop.o ipadm_persist.o ipadm_addr.o ipadm_if.o \ + ipadm_ndpd.o + +include ../../Makefile.lib + +# install this library in the root filesystem +include ../../Makefile.rootfs + +LIBS = $(DYNLIB) $(LINTLIB) +LDLIBS += -lc -lnsl -linetutil -lsocket -ldlpi -lnvpair -ldhcpagent \ + -ldladm -lsecdb + +SRCDIR = ../common +$(LINTLIB) := SRCS = $(SRCDIR)/$(LINTSRC) + +CFLAGS += $(CCVERBOSE) +CPPFLAGS += -I$(SRCDIR) -D_REENTRANT + +.KEEP_STATE: + +all: $(LIBS) + +lint: lintcheck + +include $(SRC)/lib/Makefile.targ diff --git a/usr/src/lib/libipadm/common/ipadm_addr.c b/usr/src/lib/libipadm/common/ipadm_addr.c new file mode 100644 index 0000000000..fb39cb1a24 --- /dev/null +++ b/usr/src/lib/libipadm/common/ipadm_addr.c @@ -0,0 +1,3466 @@ +/* + * 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. + */ + +/* + * This file contains functions for address management such as creating + * an address, deleting an address, enabling an address, disabling an + * address, bringing an address down or up, setting/getting properties + * on an address object and listing address information + * for all addresses in active as well as persistent configuration. + */ +#include <sys/types.h> +#include <sys/socket.h> +#include <netdb.h> +#include <inet/ip.h> +#include <string.h> +#include <strings.h> +#include <assert.h> +#include <sys/sockio.h> +#include <errno.h> +#include <unistd.h> +#include <stropts.h> +#include <zone.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <fcntl.h> +#include <ctype.h> +#include <dhcpagent_util.h> +#include <dhcpagent_ipc.h> +#include <ipadm_ndpd.h> +#include <libdladm.h> +#include <libdllink.h> +#include <libdliptun.h> +#include <ifaddrs.h> +#include "libipadm_impl.h" + +#define SIN6(a) ((struct sockaddr_in6 *)a) +#define SIN(a) ((struct sockaddr_in *)a) + +static ipadm_status_t i_ipadm_create_addr(ipadm_handle_t, ipadm_addrobj_t, + uint32_t); +static ipadm_status_t i_ipadm_create_dhcp(ipadm_handle_t, ipadm_addrobj_t, + uint32_t); +static ipadm_status_t i_ipadm_delete_dhcp(ipadm_handle_t, ipadm_addrobj_t, + boolean_t); +static ipadm_status_t i_ipadm_get_db_addr(ipadm_handle_t, const char *, + const char *, nvlist_t **); +static ipadm_status_t i_ipadm_op_dhcp(ipadm_addrobj_t, dhcp_ipc_type_t, + int *); +static ipadm_status_t i_ipadm_validate_create_addr(ipadm_handle_t, + ipadm_addrobj_t, uint32_t); +static ipadm_status_t i_ipadm_addr_persist_nvl(ipadm_handle_t, nvlist_t *, + uint32_t); +static ipadm_status_t i_ipadm_get_default_prefixlen(struct sockaddr_storage *, + uint32_t *); +static ipadm_status_t i_ipadm_get_static_addr_db(ipadm_handle_t, + ipadm_addrobj_t); +static boolean_t i_ipadm_is_user_aobjname_valid(const char *); + +/* + * Callback functions to retrieve property values from the kernel. These + * functions, when required, translate the values from the kernel to a format + * suitable for printing. They also retrieve DEFAULT, PERM and POSSIBLE values + * for a given property. + */ +static ipadm_pd_getf_t i_ipadm_get_prefixlen, i_ipadm_get_addr_flag, + i_ipadm_get_zone, i_ipadm_get_broadcast; + +/* + * Callback functions to set property values. These functions translate the + * values to a format suitable for kernel consumption, allocate the necessary + * ioctl buffers and then invoke ioctl(). + */ +static ipadm_pd_setf_t i_ipadm_set_prefixlen, i_ipadm_set_addr_flag, + i_ipadm_set_zone; + +/* address properties description table */ +ipadm_prop_desc_t ipadm_addrprop_table[] = { + { "broadcast", IPADMPROP_CLASS_ADDR, MOD_PROTO_NONE, + NULL, NULL, i_ipadm_get_broadcast }, + + { "deprecated", IPADMPROP_CLASS_ADDR, MOD_PROTO_NONE, + i_ipadm_set_addr_flag, i_ipadm_get_onoff, + i_ipadm_get_addr_flag }, + + { "prefixlen", IPADMPROP_CLASS_ADDR, MOD_PROTO_NONE, + i_ipadm_set_prefixlen, i_ipadm_get_prefixlen, + i_ipadm_get_prefixlen }, + + { "private", IPADMPROP_CLASS_ADDR, MOD_PROTO_NONE, + i_ipadm_set_addr_flag, i_ipadm_get_onoff, i_ipadm_get_addr_flag }, + + { "transmit", IPADMPROP_CLASS_ADDR, MOD_PROTO_NONE, + i_ipadm_set_addr_flag, i_ipadm_get_onoff, i_ipadm_get_addr_flag }, + + { "zone", IPADMPROP_CLASS_ADDR, MOD_PROTO_NONE, + i_ipadm_set_zone, NULL, i_ipadm_get_zone }, + + { NULL, 0, 0, NULL, NULL, NULL } +}; + +static ipadm_prop_desc_t up_addrprop = { "up", IPADMPROP_CLASS_ADDR, + MOD_PROTO_NONE, NULL, NULL, NULL }; + +/* + * Helper function that initializes the `ipadm_ifname', `ipadm_aobjname', and + * `ipadm_atype' fields of the given `ipaddr'. + */ +void +i_ipadm_init_addr(ipadm_addrobj_t ipaddr, const char *ifname, + const char *aobjname, ipadm_addr_type_t atype) +{ + bzero(ipaddr, sizeof (struct ipadm_addrobj_s)); + (void) strlcpy(ipaddr->ipadm_ifname, ifname, + sizeof (ipaddr->ipadm_ifname)); + (void) strlcpy(ipaddr->ipadm_aobjname, aobjname, + sizeof (ipaddr->ipadm_aobjname)); + ipaddr->ipadm_atype = atype; +} + +/* + * Determine the permission of the property depending on whether it has a + * set() and/or get() callback functions. + */ +static ipadm_status_t +i_ipadm_pd2permstr(ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize) +{ + uint_t perm; + size_t nbytes; + + perm = 0; + if (pdp->ipd_set != NULL) + perm |= MOD_PROP_PERM_WRITE; + if (pdp->ipd_get != NULL) + perm |= MOD_PROP_PERM_READ; + + nbytes = snprintf(buf, *bufsize, "%c%c", + ((perm & MOD_PROP_PERM_READ) != 0) ? 'r' : '-', + ((perm & MOD_PROP_PERM_WRITE) != 0) ? 'w' : '-'); + + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + return (IPADM_SUCCESS); +} + +/* + * Given an addrobj with `ipadm_aobjname' filled in, i_ipadm_get_addrobj() + * retrieves the information necessary for any operation on the object, + * such as delete-addr, enable-addr, disable-addr, up-addr, down-addr, + * refresh-addr, get-addrprop or set-addrprop. The information include + * the logical interface number, address type, address family, + * the interface id (if the address type is IPADM_ADDR_IPV6_ADDRCONF) and + * the ipadm_flags that indicate if the address is present in + * active configuration or persistent configuration or both. If the address + * is not found, IPADM_NOTSUP is returned. + */ +ipadm_status_t +i_ipadm_get_addrobj(ipadm_handle_t iph, ipadm_addrobj_t ipaddr) +{ + ipmgmt_aobjop_arg_t larg; + ipmgmt_aobjop_rval_t rval, *rvalp; + int err = 0; + + /* populate the door_call argument structure */ + larg.ia_cmd = IPMGMT_CMD_AOBJNAME2ADDROBJ; + (void) strlcpy(larg.ia_aobjname, ipaddr->ipadm_aobjname, + sizeof (larg.ia_aobjname)); + + rvalp = &rval; + err = ipadm_door_call(iph, &larg, sizeof (larg), (void **)&rvalp, + sizeof (rval), B_FALSE); + if (err != 0) + return (ipadm_errno2status(err)); + (void) strlcpy(ipaddr->ipadm_ifname, rval.ir_ifname, + sizeof (ipaddr->ipadm_ifname)); + ipaddr->ipadm_lifnum = rval.ir_lnum; + ipaddr->ipadm_atype = rval.ir_atype; + ipaddr->ipadm_af = rval.ir_family; + ipaddr->ipadm_flags = rval.ir_flags; + if (rval.ir_atype == IPADM_ADDR_IPV6_ADDRCONF) { + (void) memcpy(&ipaddr->ipadm_intfid, &rval.ir_ifid, + sizeof (ipaddr->ipadm_intfid)); + } + + return (IPADM_SUCCESS); +} + +/* + * Retrieves the static address (IPv4 or IPv6) for the given address object + * in `ipaddr' from persistent DB. + */ +static ipadm_status_t +i_ipadm_get_static_addr_db(ipadm_handle_t iph, ipadm_addrobj_t ipaddr) +{ + ipadm_status_t status; + nvlist_t *onvl; + nvlist_t *anvl = NULL; + nvlist_t *nvladdr; + nvpair_t *nvp; + char *name; + char *aobjname = ipaddr->ipadm_aobjname; + char *sname; + sa_family_t af = AF_UNSPEC; + + /* + * Get the address line in the nvlist `onvl' from ipmgmtd daemon. + */ + status = i_ipadm_get_db_addr(iph, NULL, aobjname, &onvl); + if (status != IPADM_SUCCESS) + return (status); + /* + * Walk through the nvlist `onvl' to extract the IPADM_NVP_IPV4ADDR + * or the IPADM_NVP_IPV6ADDR name-value pair. + */ + for (nvp = nvlist_next_nvpair(onvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(onvl, NULL)) { + if (nvpair_value_nvlist(nvp, &anvl) != 0) + continue; + if (nvlist_exists(anvl, IPADM_NVP_IPV4ADDR) || + nvlist_exists(anvl, IPADM_NVP_IPV6ADDR)) + break; + } + if (nvp == NULL) + goto fail; + for (nvp = nvlist_next_nvpair(anvl, NULL); + nvp != NULL; nvp = nvlist_next_nvpair(anvl, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_IPV4ADDR) == 0) { + af = AF_INET; + break; + } else if (strcmp(name, IPADM_NVP_IPV6ADDR) == 0) { + af = AF_INET6; + break; + } + } + assert(af != AF_UNSPEC); + if (nvpair_value_nvlist(nvp, &nvladdr) != 0 || + nvlist_lookup_string(nvladdr, IPADM_NVP_IPADDRHNAME, &sname) != 0 || + ipadm_set_addr(ipaddr, sname, af) != IPADM_SUCCESS) { + goto fail; + } + nvlist_free(onvl); + return (IPADM_SUCCESS); +fail: + nvlist_free(onvl); + return (IPADM_NOTFOUND); +} + +/* + * For the given `addrobj->ipadm_lifnum' and `addrobj->ipadm_af', this function + * fills in the address objname, the address type and the ipadm_flags. + */ +ipadm_status_t +i_ipadm_get_lif2addrobj(ipadm_handle_t iph, ipadm_addrobj_t addrobj) +{ + ipmgmt_aobjop_arg_t larg; + ipmgmt_aobjop_rval_t rval, *rvalp; + int err; + + larg.ia_cmd = IPMGMT_CMD_LIF2ADDROBJ; + (void) strlcpy(larg.ia_ifname, addrobj->ipadm_ifname, + sizeof (larg.ia_ifname)); + larg.ia_lnum = addrobj->ipadm_lifnum; + larg.ia_family = addrobj->ipadm_af; + + rvalp = &rval; + err = ipadm_door_call(iph, &larg, sizeof (larg), (void **)&rvalp, + sizeof (rval), B_FALSE); + if (err != 0) + return (ipadm_errno2status(err)); + (void) strlcpy(addrobj->ipadm_aobjname, rval.ir_aobjname, + sizeof (addrobj->ipadm_aobjname)); + addrobj->ipadm_atype = rval.ir_atype; + addrobj->ipadm_flags = rval.ir_flags; + + return (IPADM_SUCCESS); +} + +/* + * Adds an addrobj to ipmgmtd daemon's aobjmap (active configuration). + * with the given name and logical interface number. + * This API is called by in.ndpd to add addrobjs when new prefixes or + * dhcpv6 addresses are configured. + */ +ipadm_status_t +ipadm_add_aobjname(ipadm_handle_t iph, const char *ifname, sa_family_t af, + const char *aobjname, ipadm_addr_type_t atype, int lnum) +{ + ipmgmt_aobjop_arg_t larg; + int err; + + larg.ia_cmd = IPMGMT_CMD_ADDROBJ_ADD; + (void) strlcpy(larg.ia_ifname, ifname, sizeof (larg.ia_ifname)); + (void) strlcpy(larg.ia_aobjname, aobjname, sizeof (larg.ia_aobjname)); + larg.ia_atype = atype; + larg.ia_lnum = lnum; + larg.ia_family = af; + err = ipadm_door_call(iph, &larg, sizeof (larg), NULL, 0, B_FALSE); + return (ipadm_errno2status(err)); +} + +/* + * Deletes an address object with given name and logical number from ipmgmtd + * daemon's aobjmap (active configuration). This API is called by in.ndpd to + * remove addrobjs when auto-configured prefixes or dhcpv6 addresses are + * removed. + */ +ipadm_status_t +ipadm_delete_aobjname(ipadm_handle_t iph, const char *ifname, sa_family_t af, + const char *aobjname, ipadm_addr_type_t atype, int lnum) +{ + struct ipadm_addrobj_s aobj; + + i_ipadm_init_addr(&aobj, ifname, aobjname, atype); + aobj.ipadm_af = af; + aobj.ipadm_lifnum = lnum; + return (i_ipadm_delete_addrobj(iph, &aobj, IPADM_OPT_ACTIVE)); +} + +/* + * Gets all the addresses from active configuration and populates the + * address information in `addrinfo'. + */ +static ipadm_status_t +i_ipadm_active_addr_info(ipadm_handle_t iph, const char *ifname, + ipadm_addr_info_t **addrinfo, uint32_t ipadm_flags, int64_t lifc_flags) +{ + ipadm_status_t status; + struct ifaddrs *ifap, *ifa; + ipadm_addr_info_t *curr, *prev = NULL; + struct ifaddrs *cifaddr; + struct lifreq lifr; + int sock; + uint64_t flags; + char cifname[LIFNAMSIZ]; + struct sockaddr_in6 *sin6; + struct ipadm_addrobj_s ipaddr; + char *sep; + int lnum; + +retry: + *addrinfo = NULL; + + /* Get all the configured addresses */ + if (getallifaddrs(AF_UNSPEC, &ifa, lifc_flags) < 0) + return (ipadm_errno2status(errno)); + /* Return if there is nothing to process. */ + if (ifa == NULL) + return (IPADM_SUCCESS); + bzero(&lifr, sizeof (lifr)); + for (ifap = ifa; ifap != NULL; ifap = ifap->ifa_next) { + (void) strlcpy(cifname, ifap->ifa_name, sizeof (cifname)); + lnum = 0; + if ((sep = strrchr(cifname, ':')) != NULL) { + *sep++ = '\0'; + lnum = atoi(sep); + } + if (ifname != NULL && strcmp(cifname, ifname) != 0) + continue; + if (!(ipadm_flags & IPADM_OPT_ZEROADDR)) { + /* + * Do not list it if it is zero, unless + * it is under DHCP or has a non-zero + * destination address. + */ + if (sockaddrunspec(ifap->ifa_addr) && + (!(ifap->ifa_flags & IFF_DHCPRUNNING) && + (!(ifap->ifa_flags & IFF_POINTOPOINT) || + sockaddrunspec(ifap->ifa_dstaddr)))) { + continue; + } + } + + /* Allocate and populate the current node in the list. */ + if ((curr = calloc(1, sizeof (ipadm_addr_info_t))) == NULL) + goto fail; + + /* Link to the list in `addrinfo'. */ + if (prev != NULL) + prev->ia_ifa.ifa_next = &curr->ia_ifa; + else + *addrinfo = curr; + prev = curr; + + cifaddr = &curr->ia_ifa; + if ((cifaddr->ifa_name = strdup(ifap->ifa_name)) == NULL) + goto fail; + cifaddr->ifa_flags = ifap->ifa_flags; + cifaddr->ifa_addr = malloc(sizeof (struct sockaddr_storage)); + if (cifaddr->ifa_addr == NULL) + goto fail; + *cifaddr->ifa_addr = *ifap->ifa_addr; + cifaddr->ifa_netmask = malloc(sizeof (struct sockaddr_storage)); + if (cifaddr->ifa_netmask == NULL) + goto fail; + *cifaddr->ifa_netmask = *ifap->ifa_netmask; + if (ifap->ifa_flags & IFF_POINTOPOINT) { + cifaddr->ifa_dstaddr = malloc( + sizeof (struct sockaddr_storage)); + if (cifaddr->ifa_dstaddr == NULL) + goto fail; + *cifaddr->ifa_dstaddr = *ifap->ifa_dstaddr; + } else if (ifap->ifa_flags & IFF_BROADCAST) { + cifaddr->ifa_broadaddr = malloc( + sizeof (struct sockaddr_storage)); + if (cifaddr->ifa_broadaddr == NULL) + goto fail; + *cifaddr->ifa_broadaddr = *ifap->ifa_broadaddr; + } + /* Get the addrobj name stored for this logical interface. */ + ipaddr.ipadm_aobjname[0] = '\0'; + (void) strlcpy(ipaddr.ipadm_ifname, cifname, + sizeof (ipaddr.ipadm_ifname)); + ipaddr.ipadm_lifnum = lnum; + ipaddr.ipadm_af = ifap->ifa_addr->ss_family; + status = i_ipadm_get_lif2addrobj(iph, &ipaddr); + + /* + * Find address type from ifa_flags, if we could not get it + * from daemon. + */ + sin6 = SIN6(ifap->ifa_addr); + flags = ifap->ifa_flags; + if (status == IPADM_SUCCESS) { + (void) strlcpy(curr->ia_aobjname, ipaddr.ipadm_aobjname, + sizeof (curr->ia_aobjname)); + curr->ia_atype = ipaddr.ipadm_atype; + } else if ((flags & IFF_DHCPRUNNING) && (!(flags & IFF_IPV6) || + !IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))) { + curr->ia_atype = IPADM_ADDR_DHCP; + } else if (flags & IFF_ADDRCONF) { + curr->ia_atype = IPADM_ADDR_IPV6_ADDRCONF; + } else { + curr->ia_atype = IPADM_ADDR_STATIC; + } + /* + * Populate the flags for the active configuration from the + * `ifa_flags'. + */ + if (!(flags & IFF_UP)) { + if (flags & IFF_DUPLICATE) + curr->ia_state = IFA_DUPLICATE; + else + curr->ia_state = IFA_DOWN; + } else { + curr->ia_cflags |= IA_UP; + if (flags & IFF_RUNNING) { + (void) strlcpy(lifr.lifr_name, ifap->ifa_name, + sizeof (lifr.lifr_name)); + sock = (ifap->ifa_addr->ss_family == AF_INET) ? + iph->iph_sock : iph->iph_sock6; + if (ioctl(sock, SIOCGLIFDADSTATE, + (caddr_t)&lifr) < 0) { + if (errno == ENXIO) { + freeifaddrs(ifa); + ipadm_free_addr_info(*addrinfo); + goto retry; + } + goto fail; + } + if (lifr.lifr_dadstate == DAD_IN_PROGRESS) + curr->ia_state = IFA_TENTATIVE; + else + curr->ia_state = IFA_OK; + } else { + curr->ia_state = IFA_INACCESSIBLE; + } + } + if (flags & IFF_UNNUMBERED) + curr->ia_cflags |= IA_UNNUMBERED; + if (flags & IFF_PRIVATE) + curr->ia_cflags |= IA_PRIVATE; + if (flags & IFF_TEMPORARY) + curr->ia_cflags |= IA_TEMPORARY; + if (flags & IFF_DEPRECATED) + curr->ia_cflags |= IA_DEPRECATED; + + } + + freeifaddrs(ifa); + return (IPADM_SUCCESS); + +fail: + /* On error, cleanup everything and return. */ + ipadm_free_addr_info(*addrinfo); + *addrinfo = NULL; + freeifaddrs(ifa); + return (ipadm_errno2status(errno)); +} + +/* + * From the given `name', i_ipadm_name2atype() deduces the address type + * and address family. If the `name' implies an address, it returns B_TRUE. + * Else, returns B_FALSE and leaves the output parameters unchanged. + */ +boolean_t +i_ipadm_name2atype(const char *name, sa_family_t *af, ipadm_addr_type_t *type) +{ + boolean_t is_addr = B_TRUE; + + if (strcmp(name, IPADM_NVP_IPV4ADDR) == 0) { + *af = AF_INET; + *type = IPADM_ADDR_STATIC; + } else if (strcmp(name, IPADM_NVP_IPV6ADDR) == 0) { + *af = AF_INET6; + *type = IPADM_ADDR_STATIC; + } else if (strcmp(name, IPADM_NVP_DHCP) == 0) { + *af = AF_INET; + *type = IPADM_ADDR_DHCP; + } else if (strcmp(name, IPADM_NVP_INTFID) == 0) { + *af = AF_INET6; + *type = IPADM_ADDR_IPV6_ADDRCONF; + } else { + is_addr = B_FALSE; + } + + return (is_addr); +} + +/* + * Parses the given nvlist `nvl' for an address or an address property. + * The input nvlist must contain either an address or an address property. + * `ainfo' is an input as well as output parameter. When an address or an + * address property is found, `ainfo' is updated with the information found. + * Some of the fields may be already filled in by the calling function. + * + * The fields that will be filled/updated by this function are `ia_pflags', + * `ia_sname' and `ia_dname'. Values for `ia_pflags' are obtained if the `nvl' + * contains an address property. `ia_sname', `ia_dname', and `ia_pflags' are + * obtained if `nvl' contains an address. + */ +static ipadm_status_t +i_ipadm_nvl2ainfo_common(nvlist_t *nvl, ipadm_addr_info_t *ainfo) +{ + nvlist_t *nvladdr; + char *name; + char *propstr = NULL; + char *sname, *dname; + nvpair_t *nvp; + sa_family_t af; + ipadm_addr_type_t atype; + boolean_t is_addr = B_FALSE; + int err; + + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + name = nvpair_name(nvp); + if (i_ipadm_name2atype(name, &af, &atype)) { + err = nvpair_value_nvlist(nvp, &nvladdr); + is_addr = B_TRUE; + } else if (IPADM_PRIV_NVP(name)) { + continue; + } else { + err = nvpair_value_string(nvp, &propstr); + } + if (err != 0) + return (ipadm_errno2status(err)); + } + + if (is_addr) { + /* + * We got an address from the nvlist `nvl'. + * Parse `nvladdr' and populate relevant information + * in `ainfo'. + */ + switch (atype) { + case IPADM_ADDR_STATIC: + if (strcmp(name, "up") == 0 && + strcmp(propstr, "yes") == 0) { + ainfo->ia_pflags |= IA_UP; + } + /* + * For static addresses, we need to get the hostnames. + */ + err = nvlist_lookup_string(nvladdr, + IPADM_NVP_IPADDRHNAME, &sname); + if (err != 0) + return (ipadm_errno2status(err)); + (void) strlcpy(ainfo->ia_sname, sname, + sizeof (ainfo->ia_sname)); + err = nvlist_lookup_string(nvladdr, + IPADM_NVP_IPDADDRHNAME, &dname); + if (err == 0) { + (void) strlcpy(ainfo->ia_dname, dname, + sizeof (ainfo->ia_dname)); + } + break; + case IPADM_ADDR_DHCP: + case IPADM_ADDR_IPV6_ADDRCONF: + /* + * dhcp and addrconf address objects are always + * marked up when re-enabled. + */ + ainfo->ia_pflags |= IA_UP; + break; + default: + return (IPADM_FAILURE); + } + } else { + /* + * We got an address property from `nvl'. Parse the + * name and the property value. Update the `ainfo->ia_pflags' + * for the flags. + */ + if (strcmp(name, "deprecated") == 0) { + if (strcmp(propstr, IPADM_ONSTR) == 0) + ainfo->ia_pflags |= IA_DEPRECATED; + } else if (strcmp(name, "private") == 0) { + if (strcmp(propstr, IPADM_ONSTR) == 0) + ainfo->ia_pflags |= IA_PRIVATE; + } + } + + return (IPADM_SUCCESS); +} + +/* + * Parses the given nvlist `nvl' for an address or an address property. + * The input nvlist must contain either an address or an address property. + * `ainfo' is an input as well as output parameter. When an address or an + * address property is found, `ainfo' is updated with the information found. + * Some of the fields may be already filled in by the calling function, + * because of previous calls to i_ipadm_nvl2ainfo_active(). + * + * Since the address object in `nvl' is also in the active configuration, the + * fields that will be filled/updated by this function are `ia_pflags', + * `ia_sname' and `ia_dname'. + * + * If this function returns an error, the calling function will take + * care of freeing the fields in `ainfo'. + */ +static ipadm_status_t +i_ipadm_nvl2ainfo_active(nvlist_t *nvl, ipadm_addr_info_t *ainfo) +{ + return (i_ipadm_nvl2ainfo_common(nvl, ainfo)); +} + +/* + * Parses the given nvlist `nvl' for an address or an address property. + * The input nvlist must contain either an address or an address property. + * `ainfo' is an input as well as output parameter. When an address or an + * address property is found, `ainfo' is updated with the information found. + * Some of the fields may be already filled in by the calling function, + * because of previous calls to i_ipadm_nvl2ainfo_persist(). + * + * All the relevant fields in `ainfo' will be filled by this function based + * on what we find in `nvl'. + * + * If this function returns an error, the calling function will take + * care of freeing the fields in `ainfo'. + */ +static ipadm_status_t +i_ipadm_nvl2ainfo_persist(nvlist_t *nvl, ipadm_addr_info_t *ainfo) +{ + nvlist_t *nvladdr; + struct ifaddrs *ifa; + char *name; + char *ifname = NULL; + char *aobjname = NULL; + char *propstr = NULL; + nvpair_t *nvp; + sa_family_t af; + ipadm_addr_type_t atype; + boolean_t is_addr = B_FALSE; + size_t size = sizeof (struct sockaddr_storage); + struct sockaddr_in6 *sin6; + uint32_t plen = 0; + int err; + ipadm_status_t status; + + status = i_ipadm_nvl2ainfo_common(nvl, ainfo); + if (status != IPADM_SUCCESS) + return (status); + + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_IFNAME) == 0) { + err = nvpair_value_string(nvp, &ifname); + } else if (strcmp(name, IPADM_NVP_AOBJNAME) == 0) { + err = nvpair_value_string(nvp, &aobjname); + } else if (i_ipadm_name2atype(name, &af, &atype)) { + err = nvpair_value_nvlist(nvp, &nvladdr); + is_addr = B_TRUE; + } else { + err = nvpair_value_string(nvp, &propstr); + } + if (err != 0) + return (ipadm_errno2status(err)); + } + + ifa = &ainfo->ia_ifa; + (void) strlcpy(ainfo->ia_aobjname, aobjname, + sizeof (ainfo->ia_aobjname)); + if (ifa->ifa_name == NULL && (ifa->ifa_name = strdup(ifname)) == NULL) + return (IPADM_NO_MEMORY); + if (is_addr) { + /* + * We got an address from the nvlist `nvl'. + * Parse `nvladdr' and populate `ifa->ifa_addr'. + */ + ainfo->ia_atype = atype; + if ((ifa->ifa_addr = calloc(1, size)) == NULL) + return (IPADM_NO_MEMORY); + switch (atype) { + case IPADM_ADDR_STATIC: + ifa->ifa_addr->ss_family = af; + break; + case IPADM_ADDR_DHCP: + ifa->ifa_addr->ss_family = AF_INET; + break; + case IPADM_ADDR_IPV6_ADDRCONF: + sin6 = SIN6(ifa->ifa_addr); + sin6->sin6_family = AF_INET6; + if (i_ipadm_nvl2in6_addr(nvladdr, IPADM_NVP_IPNUMADDR, + &sin6->sin6_addr) != IPADM_SUCCESS) + return (IPADM_NO_MEMORY); + err = nvlist_lookup_uint32(nvladdr, IPADM_NVP_PREFIXLEN, + &plen); + if (err != 0) + return (ipadm_errno2status(err)); + if ((ifa->ifa_netmask = malloc(size)) == NULL) + return (IPADM_NO_MEMORY); + if ((err = plen2mask(plen, af, ifa->ifa_netmask)) != 0) + return (ipadm_errno2status(err)); + break; + default: + return (IPADM_FAILURE); + } + } else { + if (strcmp(name, "prefixlen") == 0) { + /* + * If a prefixlen was found, update the + * `ainfo->ia_ifa.ifa_netmask'. + */ + + if ((ifa->ifa_netmask = malloc(size)) == NULL) + return (IPADM_NO_MEMORY); + /* + * Address property lines always follow the address + * line itself in the persistent db. We must have + * found a valid `ainfo->ia_ifa.ifa_addr' by now. + */ + assert(ifa->ifa_addr != NULL); + err = plen2mask(atoi(propstr), ifa->ifa_addr->ss_family, + ifa->ifa_netmask); + if (err != 0) + return (ipadm_errno2status(err)); + } + } + + return (IPADM_SUCCESS); +} + +/* + * Retrieves all addresses from active config and appends to it the + * addresses that are found only in persistent config. In addition, + * it updates the persistent fields for each address from information + * found in persistent config. The output parameter `addrinfo' contains + * complete information regarding all addresses in active as well as + * persistent config. + */ +static ipadm_status_t +i_ipadm_get_all_addr_info(ipadm_handle_t iph, const char *ifname, + ipadm_addr_info_t **addrinfo, uint32_t ipadm_flags, int64_t lifc_flags) +{ + nvlist_t *nvladdr = NULL; + nvlist_t *onvl = NULL; + nvpair_t *nvp; + ipadm_status_t status; + ipadm_addr_info_t *ainfo = NULL; + ipadm_addr_info_t *curr; + ipadm_addr_info_t *last = NULL; + char *aobjname; + + /* Get all addresses from active config. */ + status = i_ipadm_active_addr_info(iph, ifname, &ainfo, ipadm_flags, + lifc_flags); + if (status != IPADM_SUCCESS) + goto fail; + + /* Get all addresses from persistent config. */ + status = i_ipadm_get_db_addr(iph, ifname, NULL, &onvl); + /* + * If no address was found in persistent config, just + * return what we found in active config. + */ + if (status == IPADM_NOTFOUND) { + /* + * If nothing was found neither active nor persistent + * config, this means that the interface does not exist, + * if one was provided in `ifname'. + */ + if (ainfo == NULL && ifname != NULL) + return (IPADM_ENXIO); + *addrinfo = ainfo; + return (IPADM_SUCCESS); + } + /* In case of any other error, cleanup and return. */ + if (status != IPADM_SUCCESS) + goto fail; + /* we append to make sure, loopback addresses are first */ + if (ainfo != NULL) { + for (curr = ainfo; IA_NEXT(curr) != NULL; curr = IA_NEXT(curr)) + ; + last = curr; + } + + /* + * `onvl' will contain all the address lines from the db. Each line + * could contain the address itself or an address property. Addresses + * and address properties are found in separate lines. + * + * If an address A was found in active, we will already have `ainfo', + * and it is present in persistent configuration as well, we need to + * update `ainfo' with persistent information (`ia_pflags). + * For each address B found only in persistent configuration, + * append the address to the list with the address info for B from + * `onvl'. + */ + for (nvp = nvlist_next_nvpair(onvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(onvl, nvp)) { + if (nvpair_value_nvlist(nvp, &nvladdr) != 0) + continue; + if (nvlist_lookup_string(nvladdr, IPADM_NVP_AOBJNAME, + &aobjname) != 0) + continue; + for (curr = ainfo; curr != NULL; curr = IA_NEXT(curr)) { + if (strcmp(curr->ia_aobjname, aobjname) == 0) + break; + } + if (curr == NULL) { + /* + * We did not find this address object in `ainfo'. + * This means that the address object exists only + * in the persistent configuration. Get its + * details and append to `ainfo'. + */ + curr = calloc(1, sizeof (ipadm_addr_info_t)); + if (curr == NULL) + goto fail; + curr->ia_state = IFA_DISABLED; + if (last != NULL) + last->ia_ifa.ifa_next = &curr->ia_ifa; + else + ainfo = curr; + last = curr; + } + /* + * Fill relevant fields of `curr' from the persistent info + * in `nvladdr'. Call the appropriate function based on the + * `ia_state' value. + */ + if (curr->ia_state == IFA_DISABLED) + status = i_ipadm_nvl2ainfo_persist(nvladdr, curr); + else + status = i_ipadm_nvl2ainfo_active(nvladdr, curr); + if (status != IPADM_SUCCESS) + goto fail; + } + *addrinfo = ainfo; + nvlist_free(onvl); + return (status); +fail: + /* On error, cleanup and return. */ + nvlist_free(onvl); + ipadm_free_addr_info(ainfo); + *addrinfo = NULL; + return (status); +} + +/* + * Callback function that sets the property `prefixlen' on the address + * object in `arg' to the value in `pval'. + */ +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_prefixlen(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t af, uint_t flags) +{ + struct sockaddr_storage netmask; + struct lifreq lifr; + int err, s; + unsigned long prefixlen, abits; + char *end; + ipadm_addrobj_t ipaddr = (ipadm_addrobj_t)arg; + + if (ipaddr->ipadm_atype == IPADM_ADDR_DHCP) + return (IPADM_NOTSUP); + + errno = 0; + prefixlen = strtoul(pval, &end, 10); + if (errno != 0 || *end != '\0') + return (IPADM_INVALID_ARG); + + abits = (af == AF_INET ? IP_ABITS : IPV6_ABITS); + if (prefixlen == 0 || prefixlen == (abits - 1)) + return (IPADM_INVALID_ARG); + + if ((err = plen2mask(prefixlen, af, &netmask)) != 0) + return (ipadm_errno2status(err)); + + s = (af == AF_INET ? iph->iph_sock : iph->iph_sock6); + + bzero(&lifr, sizeof (lifr)); + i_ipadm_addrobj2lifname(ipaddr, lifr.lifr_name, + sizeof (lifr.lifr_name)); + (void) memcpy(&lifr.lifr_addr, &netmask, sizeof (netmask)); + if (ioctl(s, SIOCSLIFNETMASK, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + + /* now, change the broadcast address to reflect the prefixlen */ + if (af == AF_INET) { + /* + * get the interface address and set it, this should reset + * the broadcast address. + */ + (void) ioctl(s, SIOCGLIFADDR, (caddr_t)&lifr); + (void) ioctl(s, SIOCSLIFADDR, (caddr_t)&lifr); + } + + return (IPADM_SUCCESS); +} + + +/* + * Callback function that sets the given value `pval' to one of the + * properties among `deprecated', `private', and `transmit' as defined in + * `pdp', on the address object in `arg'. + */ +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_addr_flag(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t af, uint_t flags) +{ + char lifname[LIFNAMSIZ]; + uint64_t on_flags = 0, off_flags = 0; + boolean_t on; + ipadm_addrobj_t ipaddr = (ipadm_addrobj_t)arg; + + if (ipaddr->ipadm_atype == IPADM_ADDR_DHCP && + strcmp(pdp->ipd_name, "deprecated") == 0) + return (IPADM_NOTSUP); + + if (strcmp(pval, IPADM_ONSTR) == 0) + on = B_TRUE; + else if (strcmp(pval, IPADM_OFFSTR) == 0) + on = B_FALSE; + else + return (IPADM_INVALID_ARG); + + if (strcmp(pdp->ipd_name, "private") == 0) { + if (on) + on_flags = IFF_PRIVATE; + else + off_flags = IFF_PRIVATE; + } else if (strcmp(pdp->ipd_name, "transmit") == 0) { + if (on) + off_flags = IFF_NOXMIT; + else + on_flags = IFF_NOXMIT; + } else if (strcmp(pdp->ipd_name, "deprecated") == 0) { + if (on) + on_flags = IFF_DEPRECATED; + else + off_flags = IFF_DEPRECATED; + } else { + return (IPADM_PROP_UNKNOWN); + } + + i_ipadm_addrobj2lifname(ipaddr, lifname, sizeof (lifname)); + return (i_ipadm_set_flags(iph, lifname, af, on_flags, off_flags)); +} + +/* + * Callback function that sets the property `zone' on the address + * object in `arg' to the value in `pval'. + */ +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_zone(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t af, uint_t flags) +{ + struct lifreq lifr; + zoneid_t zoneid; + int s; + + /* + * To modify the zone assignment such that it persists across + * reboots, zonecfg(1M) must be used. + */ + if (flags & IPADM_OPT_PERSIST) { + return (IPADM_NOTSUP); + } else if (flags & IPADM_OPT_ACTIVE) { + /* put logical interface into all zones */ + if (strcmp(pval, "all-zones") == 0) { + zoneid = ALL_ZONES; + } else { + /* zone must be ready or running */ + if ((zoneid = getzoneidbyname(pval)) == -1) + return (ipadm_errno2status(errno)); + } + } else { + return (IPADM_INVALID_ARG); + } + + s = (af == AF_INET ? iph->iph_sock : iph->iph_sock6); + bzero(&lifr, sizeof (lifr)); + i_ipadm_addrobj2lifname((ipadm_addrobj_t)arg, lifr.lifr_name, + sizeof (lifr.lifr_name)); + lifr.lifr_zoneid = zoneid; + if (ioctl(s, SIOCSLIFZONE, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + + return (IPADM_SUCCESS); +} + +/* + * Callback function that gets the property `broadcast' for the address + * object in `arg'. + */ +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_broadcast(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t af, + uint_t valtype) +{ + struct sockaddr_in *sin; + struct lifreq lifr; + char lifname[LIFNAMSIZ]; + ipadm_addrobj_t ipaddr = (ipadm_addrobj_t)arg; + ipadm_status_t status; + size_t nbytes = 0; + uint64_t ifflags = 0; + + i_ipadm_addrobj2lifname(ipaddr, lifname, sizeof (lifname)); + if (ipaddr->ipadm_flags & IPMGMT_ACTIVE) { + status = i_ipadm_get_flags(iph, lifname, af, &ifflags); + if (status != IPADM_SUCCESS) + return (status); + if (!(ifflags & IFF_BROADCAST)) { + buf[0] = '\0'; + return (IPADM_SUCCESS); + } + } + + switch (valtype) { + case MOD_PROP_DEFAULT: { + struct sockaddr_storage mask; + struct in_addr broadaddr; + uint_t plen; + in_addr_t addr, maddr; + char val[MAXPROPVALLEN]; + uint_t valsz = MAXPROPVALLEN; + ipadm_status_t status; + int err; + struct sockaddr_in *sin; + + if (!(ipaddr->ipadm_flags & IPMGMT_ACTIVE)) { + /* + * Since the address is unknown we cannot + * obtain default prefixlen + */ + if (ipaddr->ipadm_atype == IPADM_ADDR_DHCP || + ipaddr->ipadm_af == AF_INET6) { + buf[0] = '\0'; + return (IPADM_SUCCESS); + } + /* + * For the static address, we get the address from the + * persistent db. + */ + status = i_ipadm_get_static_addr_db(iph, ipaddr); + if (status != IPADM_SUCCESS) + return (status); + sin = SIN(&ipaddr->ipadm_static_addr); + addr = sin->sin_addr.s_addr; + } else { + /* + * If the address object is active, we retrieve the + * address from kernel. + */ + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lifname, + sizeof (lifr.lifr_name)); + if (ioctl(iph->iph_sock, SIOCGLIFADDR, + (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + + addr = (SIN(&lifr.lifr_addr))->sin_addr.s_addr; + } + /* + * For default broadcast address, get the address and the + * default prefixlen for that address and then compute the + * broadcast address. + */ + status = i_ipadm_get_prefixlen(iph, arg, NULL, val, &valsz, af, + MOD_PROP_DEFAULT); + if (status != IPADM_SUCCESS) + return (status); + + plen = atoi(val); + if ((err = plen2mask(plen, AF_INET, &mask)) != 0) + return (ipadm_errno2status(err)); + maddr = (SIN(&mask))->sin_addr.s_addr; + broadaddr.s_addr = (addr & maddr) | ~maddr; + nbytes = snprintf(buf, *bufsize, "%s", inet_ntoa(broadaddr)); + break; + } + case MOD_PROP_ACTIVE: + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lifname, + sizeof (lifr.lifr_name)); + if (ioctl(iph->iph_sock, SIOCGLIFBRDADDR, + (caddr_t)&lifr) < 0) { + return (ipadm_errno2status(errno)); + } else { + sin = SIN(&lifr.lifr_addr); + nbytes = snprintf(buf, *bufsize, "%s", + inet_ntoa(sin->sin_addr)); + } + break; + default: + return (IPADM_INVALID_ARG); + } + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + return (IPADM_SUCCESS); +} + +/* + * Callback function that retrieves the value of the property `prefixlen' + * for the address object in `arg'. + */ +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_prefixlen(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t af, + uint_t valtype) +{ + struct lifreq lifr; + ipadm_addrobj_t ipaddr = (ipadm_addrobj_t)arg; + char lifname[LIFNAMSIZ]; + int s; + uint32_t prefixlen; + size_t nbytes; + ipadm_status_t status; + uint64_t lifflags; + + i_ipadm_addrobj2lifname(ipaddr, lifname, sizeof (lifname)); + if (ipaddr->ipadm_flags & IPMGMT_ACTIVE) { + status = i_ipadm_get_flags(iph, lifname, af, &lifflags); + if (status != IPADM_SUCCESS) { + return (status); + } else if (lifflags & IFF_POINTOPOINT) { + buf[0] = '\0'; + return (status); + } + } + + s = (af == AF_INET ? iph->iph_sock : iph->iph_sock6); + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lifname, sizeof (lifr.lifr_name)); + switch (valtype) { + case MOD_PROP_POSSIBLE: + if (af == AF_INET) + nbytes = snprintf(buf, *bufsize, "1-30,32"); + else + nbytes = snprintf(buf, *bufsize, "1-126,128"); + break; + case MOD_PROP_DEFAULT: + if (ipaddr->ipadm_flags & IPMGMT_ACTIVE) { + /* + * For static addresses, we retrieve the address + * from kernel if it is active. + */ + if (ioctl(s, SIOCGLIFADDR, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + status = i_ipadm_get_default_prefixlen( + &lifr.lifr_addr, &prefixlen); + if (status != IPADM_SUCCESS) + return (status); + } else if ((ipaddr->ipadm_flags & IPMGMT_PERSIST) && + ipaddr->ipadm_atype == IPADM_ADDR_DHCP) { + /* + * Since the address is unknown we cannot + * obtain default prefixlen + */ + buf[0] = '\0'; + return (IPADM_SUCCESS); + } else { + /* + * If not in active config, we use the address + * from persistent store. + */ + status = i_ipadm_get_static_addr_db(iph, ipaddr); + if (status != IPADM_SUCCESS) + return (status); + status = i_ipadm_get_default_prefixlen( + &ipaddr->ipadm_static_addr, &prefixlen); + if (status != IPADM_SUCCESS) + return (status); + } + nbytes = snprintf(buf, *bufsize, "%u", prefixlen); + break; + case MOD_PROP_ACTIVE: + if (ioctl(s, SIOCGLIFNETMASK, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + prefixlen = lifr.lifr_addrlen; + nbytes = snprintf(buf, *bufsize, "%u", prefixlen); + break; + default: + return (IPADM_INVALID_ARG); + } + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + return (IPADM_SUCCESS); +} + +/* + * Callback function that retrieves the value of one of the properties + * among `deprecated', `private', and `transmit' for the address object + * in `arg'. + */ +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_addr_flag(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t af, + uint_t valtype) +{ + boolean_t on = B_FALSE; + char lifname[LIFNAMSIZ]; + ipadm_status_t status = IPADM_SUCCESS; + uint64_t ifflags; + size_t nbytes; + ipadm_addrobj_t ipaddr = (ipadm_addrobj_t)arg; + + switch (valtype) { + case MOD_PROP_DEFAULT: + if (strcmp(pdp->ipd_name, "private") == 0 || + strcmp(pdp->ipd_name, "deprecated") == 0) { + on = B_FALSE; + } else if (strcmp(pdp->ipd_name, "transmit") == 0) { + on = B_TRUE; + } else { + return (IPADM_PROP_UNKNOWN); + } + break; + case MOD_PROP_ACTIVE: + /* + * If the address is present in active configuration, we + * retrieve it from kernel to get the property value. + * Else, there is no value to return. + */ + i_ipadm_addrobj2lifname(ipaddr, lifname, sizeof (lifname)); + status = i_ipadm_get_flags(iph, lifname, af, &ifflags); + if (status != IPADM_SUCCESS) + return (status); + if (strcmp(pdp->ipd_name, "private") == 0) + on = (ifflags & IFF_PRIVATE); + else if (strcmp(pdp->ipd_name, "transmit") == 0) + on = !(ifflags & IFF_NOXMIT); + else if (strcmp(pdp->ipd_name, "deprecated") == 0) + on = (ifflags & IFF_DEPRECATED); + break; + default: + return (IPADM_INVALID_ARG); + } + nbytes = snprintf(buf, *bufsize, "%s", + (on ? IPADM_ONSTR : IPADM_OFFSTR)); + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + status = IPADM_NO_BUFS; + } + + return (status); +} + +/* + * Callback function that retrieves the value of the property `zone' + * for the address object in `arg'. + */ +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_zone(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t af, + uint_t valtype) +{ + struct lifreq lifr; + char zone_name[ZONENAME_MAX]; + int s; + size_t nbytes = 0; + + if (getzoneid() != GLOBAL_ZONEID) { + buf[0] = '\0'; + return (IPADM_SUCCESS); + } + + /* + * we are in global zone. See if the lifname is assigned to shared-ip + * zone or global zone. + */ + switch (valtype) { + case MOD_PROP_DEFAULT: + if (getzonenamebyid(GLOBAL_ZONEID, zone_name, + sizeof (zone_name)) > 0) + nbytes = snprintf(buf, *bufsize, "%s", zone_name); + else + return (ipadm_errno2status(errno)); + break; + case MOD_PROP_ACTIVE: + bzero(&lifr, sizeof (lifr)); + i_ipadm_addrobj2lifname((ipadm_addrobj_t)arg, lifr.lifr_name, + sizeof (lifr.lifr_name)); + s = (af == AF_INET ? iph->iph_sock : iph->iph_sock6); + + if (ioctl(s, SIOCGLIFZONE, (caddr_t)&lifr) == -1) + return (ipadm_errno2status(errno)); + + if (lifr.lifr_zoneid == ALL_ZONES) { + nbytes = snprintf(buf, *bufsize, "%s", "all-zones"); + } else if (getzonenamebyid(lifr.lifr_zoneid, zone_name, + sizeof (zone_name)) < 0) { + return (ipadm_errno2status(errno)); + } else { + nbytes = snprintf(buf, *bufsize, "%s", zone_name); + } + break; + default: + return (IPADM_INVALID_ARG); + } + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + + return (IPADM_SUCCESS); +} + +static ipadm_prop_desc_t * +i_ipadm_getpropdesc(const char *pname) +{ + int i; + + for (i = 0; ipadm_addrprop_table[i].ipd_name != NULL; i++) { + if (strcmp(pname, ipadm_addrprop_table[i].ipd_name) == 0) + return (&ipadm_addrprop_table[i]); + } + return (NULL); +} + +/* + * Gets the value of the given address property `pname' for the address + * object with name `aobjname'. + */ +ipadm_status_t +ipadm_get_addrprop(ipadm_handle_t iph, const char *pname, char *buf, + uint_t *bufsize, const char *aobjname, uint_t valtype) +{ + struct ipadm_addrobj_s ipaddr; + ipadm_status_t status = IPADM_SUCCESS; + sa_family_t af; + ipadm_prop_desc_t *pdp = NULL; + + if (iph == NULL || pname == NULL || buf == NULL || + bufsize == NULL || *bufsize == 0 || aobjname == NULL) { + return (IPADM_INVALID_ARG); + } + + /* find the property in the property description table */ + if ((pdp = i_ipadm_getpropdesc(pname)) == NULL) + return (IPADM_PROP_UNKNOWN); + + /* + * For the given aobjname, get the addrobj it represents and + * retrieve the property value for that object. + */ + i_ipadm_init_addr(&ipaddr, "", aobjname, IPADM_ADDR_NONE); + if ((status = i_ipadm_get_addrobj(iph, &ipaddr)) != IPADM_SUCCESS) + return (status); + + if (ipaddr.ipadm_atype == IPADM_ADDR_IPV6_ADDRCONF) + return (IPADM_NOTSUP); + af = ipaddr.ipadm_af; + + /* + * Call the appropriate callback function to based on the field + * that was asked for. + */ + switch (valtype) { + case IPADM_OPT_PERM: + status = i_ipadm_pd2permstr(pdp, buf, bufsize); + break; + case IPADM_OPT_ACTIVE: + if (!(ipaddr.ipadm_flags & IPMGMT_ACTIVE)) { + buf[0] = '\0'; + } else { + status = pdp->ipd_get(iph, &ipaddr, pdp, buf, bufsize, + af, MOD_PROP_ACTIVE); + } + break; + case IPADM_OPT_DEFAULT: + status = pdp->ipd_get(iph, &ipaddr, pdp, buf, bufsize, + af, MOD_PROP_DEFAULT); + break; + case IPADM_OPT_POSSIBLE: + if (pdp->ipd_get_range != NULL) { + status = pdp->ipd_get_range(iph, &ipaddr, pdp, buf, + bufsize, af, MOD_PROP_POSSIBLE); + break; + } + buf[0] = '\0'; + break; + case IPADM_OPT_PERSIST: + status = i_ipadm_get_persist_propval(iph, pdp, buf, bufsize, + &ipaddr); + break; + default: + status = IPADM_INVALID_ARG; + break; + } + + return (status); +} + +/* + * Sets the value of the given address property `pname' to `pval' for the + * address object with name `aobjname'. + */ +ipadm_status_t +ipadm_set_addrprop(ipadm_handle_t iph, const char *pname, + const char *pval, const char *aobjname, uint_t pflags) +{ + struct ipadm_addrobj_s ipaddr; + sa_family_t af; + ipadm_prop_desc_t *pdp = NULL; + char defbuf[MAXPROPVALLEN]; + uint_t defbufsize = MAXPROPVALLEN; + boolean_t reset = (pflags & IPADM_OPT_DEFAULT); + ipadm_status_t status = IPADM_SUCCESS; + + /* Check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + + if (iph == NULL || pname == NULL || aobjname == NULL || pflags == 0 || + pflags == IPADM_OPT_PERSIST || + (pflags & ~(IPADM_COMMON_OPT_MASK|IPADM_OPT_DEFAULT)) || + (!reset && pval == NULL)) { + return (IPADM_INVALID_ARG); + } + + /* find the property in the property description table */ + if ((pdp = i_ipadm_getpropdesc(pname)) == NULL) + return (IPADM_PROP_UNKNOWN); + + if (pdp->ipd_set == NULL || (reset && pdp->ipd_get == NULL)) + return (IPADM_NOTSUP); + + /* + * For the given aobjname, get the addrobj it represents and + * set the property value for that object. + */ + i_ipadm_init_addr(&ipaddr, "", aobjname, IPADM_ADDR_NONE); + if ((status = i_ipadm_get_addrobj(iph, &ipaddr)) != IPADM_SUCCESS) + return (status); + + if (!(ipaddr.ipadm_flags & IPMGMT_ACTIVE)) + return (IPADM_OP_DISABLE_OBJ); + + /* Persistent operation not allowed on a temporary object. */ + if ((pflags & IPADM_OPT_PERSIST) && + !(ipaddr.ipadm_flags & IPMGMT_PERSIST)) + return (IPADM_TEMPORARY_OBJ); + + /* + * Currently, setting an address property on an address object of type + * IPADM_ADDR_IPV6_ADDRCONF is not supported. Supporting it involves + * in.ndpd retrieving the address properties from ipmgmtd for given + * address object and then setting them on auto-configured addresses, + * whenever in.ndpd gets a new prefix. This will be supported in + * future releases. + */ + if (ipaddr.ipadm_atype == IPADM_ADDR_IPV6_ADDRCONF) + return (IPADM_NOTSUP); + + /* + * Setting an address property on an address object that is + * not present in active configuration is not supported. + */ + if (!(ipaddr.ipadm_flags & IPMGMT_ACTIVE)) + return (IPADM_NOTSUP); + + af = ipaddr.ipadm_af; + if (reset) { + /* + * If we were asked to reset the value, we need to fetch + * the default value and set the default value. + */ + status = pdp->ipd_get(iph, &ipaddr, pdp, defbuf, &defbufsize, + af, MOD_PROP_DEFAULT); + if (status != IPADM_SUCCESS) + return (status); + pval = defbuf; + } + /* set the user provided or default property value */ + status = pdp->ipd_set(iph, &ipaddr, pdp, pval, af, pflags); + if (status != IPADM_SUCCESS) + return (status); + + /* + * If IPADM_OPT_PERSIST was set in `flags', we need to store + * property and its value in persistent DB. + */ + if (pflags & IPADM_OPT_PERSIST) { + status = i_ipadm_persist_propval(iph, pdp, pval, &ipaddr, + pflags); + } + + return (status); +} + +/* + * Remove the address specified by the address object in `addr' + * from kernel. If the address is on a non-zero logical interface, we do a + * SIOCLIFREMOVEIF, otherwise we set the address to INADDR_ANY for IPv4 or + * :: for IPv6. + */ +ipadm_status_t +i_ipadm_delete_addr(ipadm_handle_t iph, ipadm_addrobj_t addr) +{ + struct lifreq lifr; + int sock; + ipadm_status_t status; + + bzero(&lifr, sizeof (lifr)); + i_ipadm_addrobj2lifname(addr, lifr.lifr_name, sizeof (lifr.lifr_name)); + sock = (addr->ipadm_af == AF_INET ? iph->iph_sock : iph->iph_sock6); + if (addr->ipadm_lifnum == 0) { + /* + * Fake the deletion of the 0'th address by + * clearing IFF_UP and setting it to as 0.0.0.0 or ::. + */ + status = i_ipadm_set_flags(iph, addr->ipadm_ifname, + addr->ipadm_af, 0, IFF_UP); + if (status != IPADM_SUCCESS) + return (status); + bzero(&lifr.lifr_addr, sizeof (lifr.lifr_addr)); + lifr.lifr_addr.ss_family = addr->ipadm_af; + if (ioctl(sock, SIOCSLIFADDR, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + if (ioctl(sock, SIOCSLIFDSTADDR, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + } else if (ioctl(sock, SIOCLIFREMOVEIF, (caddr_t)&lifr) < 0) { + return (ipadm_errno2status(errno)); + } + + return (IPADM_SUCCESS); +} + +/* + * Extracts the IPv6 address from the nvlist in `nvl'. + */ +ipadm_status_t +i_ipadm_nvl2in6_addr(nvlist_t *nvl, char *addr_type, in6_addr_t *in6_addr) +{ + uint8_t *addr6; + uint_t n; + + if (nvlist_lookup_uint8_array(nvl, addr_type, &addr6, &n) != 0) + return (IPADM_NOTFOUND); + assert(n == 16); + bcopy(addr6, in6_addr->s6_addr, n); + return (IPADM_SUCCESS); +} + +/* + * Used to validate the given addrobj name string. Length of `aobjname' + * cannot exceed IPADM_AOBJ_USTRSIZ. `aobjname' should start with an + * alphabetic character and it can only contain alphanumeric characters. + */ +static boolean_t +i_ipadm_is_user_aobjname_valid(const char *aobjname) +{ + const char *cp; + + if (aobjname == NULL || strlen(aobjname) >= IPADM_AOBJ_USTRSIZ || + !isalpha(*aobjname)) { + return (B_FALSE); + } + for (cp = aobjname + 1; *cp && isalnum(*cp); cp++) + ; + return (*cp == '\0'); +} + +/* + * Computes the prefixlen for the given `addr' based on the netmask found using + * the order specified in /etc/nsswitch.conf. If not found, then the + * prefixlen is computed using the Classful subnetting semantics defined + * in RFC 791 for IPv4 and RFC 4291 for IPv6. + */ +static ipadm_status_t +i_ipadm_get_default_prefixlen(struct sockaddr_storage *addr, uint32_t *plen) +{ + sa_family_t af = addr->ss_family; + struct sockaddr_storage mask; + struct sockaddr_in *m = (struct sockaddr_in *)&mask; + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + struct in_addr ia; + uint32_t prefixlen = 0; + + switch (af) { + case AF_INET: + sin = SIN(addr); + ia.s_addr = ntohl(sin->sin_addr.s_addr); + get_netmask4(&ia, &m->sin_addr); + m->sin_addr.s_addr = htonl(m->sin_addr.s_addr); + m->sin_family = AF_INET; + prefixlen = mask2plen(&mask); + break; + case AF_INET6: + sin6 = SIN6(addr); + if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr)) + prefixlen = 10; + else + prefixlen = 64; + break; + default: + return (IPADM_INVALID_ARG); + } + *plen = prefixlen; + return (IPADM_SUCCESS); +} + +static ipadm_status_t +i_ipadm_resolve_addr(const char *name, sa_family_t af, + struct sockaddr_storage *ss) +{ + struct addrinfo hints, *ai; + int rc; + struct sockaddr_in6 *sin6; + struct sockaddr_in *sin; + boolean_t is_mapped; + + (void) memset(&hints, 0, sizeof (hints)); + hints.ai_family = af; + hints.ai_flags = (AI_ALL | AI_V4MAPPED); + rc = getaddrinfo(name, NULL, &hints, &ai); + if (rc != 0) { + if (rc == EAI_NONAME) + return (IPADM_BAD_ADDR); + else + return (IPADM_FAILURE); + } + if (ai->ai_next != NULL) { + /* maps to more than one hostname */ + freeaddrinfo(ai); + return (IPADM_BAD_HOSTNAME); + } + /* LINTED E_BAD_PTR_CAST_ALIGN */ + is_mapped = IN6_IS_ADDR_V4MAPPED(&(SIN6(ai->ai_addr))->sin6_addr); + if (is_mapped) { + sin = SIN(ss); + sin->sin_family = AF_INET; + /* LINTED E_BAD_PTR_CAST_ALIGN */ + IN6_V4MAPPED_TO_INADDR(&(SIN6(ai->ai_addr))->sin6_addr, + &sin->sin_addr); + } else { + sin6 = SIN6(ss); + sin6->sin6_family = AF_INET6; + bcopy(ai->ai_addr, sin6, sizeof (*sin6)); + } + freeaddrinfo(ai); + return (IPADM_SUCCESS); +} + +/* + * This takes a static address string <addr>[/<mask>] or a hostname + * and maps it to a single numeric IP address, consulting DNS if + * hostname was provided. If a specific address family was requested, + * an error is returned if the given hostname does not map to an address + * of the given family. Note that this function returns failure + * if the name maps to more than one IP address. + */ +ipadm_status_t +ipadm_set_addr(ipadm_addrobj_t ipaddr, const char *astr, sa_family_t af) +{ + char *prefixlenstr; + uint32_t prefixlen = 0; + char *endp; + /* + * We use (NI_MAXHOST + 5) because the longest possible + * astr will have (NI_MAXHOST + '/' + {a maximum of 32 for IPv4 + * or a maximum of 128 for IPv6 + '\0') chars + */ + char addrstr[NI_MAXHOST + 5]; + ipadm_status_t status; + + (void) snprintf(addrstr, sizeof (addrstr), "%s", astr); + if ((prefixlenstr = strchr(addrstr, '/')) != NULL) { + *prefixlenstr++ = '\0'; + errno = 0; + prefixlen = strtoul(prefixlenstr, &endp, 10); + if (errno != 0 || *endp != '\0') + return (IPADM_INVALID_ARG); + if ((af == AF_INET && prefixlen > IP_ABITS) || + (af == AF_INET6 && prefixlen > IPV6_ABITS)) + return (IPADM_INVALID_ARG); + } + + status = i_ipadm_resolve_addr(addrstr, af, &ipaddr->ipadm_static_addr); + if (status == IPADM_SUCCESS) { + (void) strlcpy(ipaddr->ipadm_static_aname, addrstr, + sizeof (ipaddr->ipadm_static_aname)); + ipaddr->ipadm_af = ipaddr->ipadm_static_addr.ss_family; + ipaddr->ipadm_static_prefixlen = prefixlen; + } + return (status); +} + +/* + * Set up tunnel destination address in ipaddr by contacting DNS. + * The function works similar to ipadm_set_addr(). + * The dst_addr must resolve to exactly one address. IPADM_BAD_ADDR is returned + * if dst_addr resolves to more than one address. The caller has to verify + * that ipadm_static_addr and ipadm_static_dst_addr have the same ss_family + */ +ipadm_status_t +ipadm_set_dst_addr(ipadm_addrobj_t ipaddr, const char *daddrstr, sa_family_t af) +{ + ipadm_status_t status; + + /* mask lengths are not meaningful for point-to-point interfaces. */ + if (strchr(daddrstr, '/') != NULL) + return (IPADM_BAD_ADDR); + + status = i_ipadm_resolve_addr(daddrstr, af, + &ipaddr->ipadm_static_dst_addr); + if (status == IPADM_SUCCESS) { + (void) strlcpy(ipaddr->ipadm_static_dname, daddrstr, + sizeof (ipaddr->ipadm_static_dname)); + } + return (status); +} + +/* + * Sets the interface ID in the address object `ipaddr' with the address + * in the string `interface_id'. This interface ID will be used when + * ipadm_create_addr() is called with `ipaddr' with address type + * set to IPADM_ADDR_IPV6_ADDRCONF. + */ +ipadm_status_t +ipadm_set_interface_id(ipadm_addrobj_t ipaddr, const char *interface_id) +{ + struct sockaddr_in6 *sin6; + char *end; + char *cp; + uint32_t prefixlen; + char addrstr[INET6_ADDRSTRLEN + 1]; + + if (ipaddr == NULL || interface_id == NULL || + ipaddr->ipadm_atype != IPADM_ADDR_IPV6_ADDRCONF) + return (IPADM_INVALID_ARG); + + (void) strlcpy(addrstr, interface_id, sizeof (addrstr)); + if ((cp = strchr(addrstr, '/')) == NULL) + return (IPADM_INVALID_ARG); + *cp++ = '\0'; + sin6 = &ipaddr->ipadm_intfid; + if (inet_pton(AF_INET6, addrstr, &sin6->sin6_addr) == 1) { + errno = 0; + prefixlen = strtoul(cp, &end, 10); + if (errno != 0 || *end != '\0' || prefixlen > IPV6_ABITS) + return (IPADM_INVALID_ARG); + sin6->sin6_family = AF_INET6; + ipaddr->ipadm_intfidlen = prefixlen; + return (IPADM_SUCCESS); + } + return (IPADM_INVALID_ARG); +} + +/* + * Sets the value for the field `ipadm_stateless' in address object `ipaddr'. + */ +ipadm_status_t +ipadm_set_stateless(ipadm_addrobj_t ipaddr, boolean_t stateless) +{ + if (ipaddr == NULL || + ipaddr->ipadm_atype != IPADM_ADDR_IPV6_ADDRCONF) + return (IPADM_INVALID_ARG); + ipaddr->ipadm_stateless = stateless; + + return (IPADM_SUCCESS); +} + +/* + * Sets the value for the field `ipadm_stateful' in address object `ipaddr'. + */ +ipadm_status_t +ipadm_set_stateful(ipadm_addrobj_t ipaddr, boolean_t stateful) +{ + if (ipaddr == NULL || + ipaddr->ipadm_atype != IPADM_ADDR_IPV6_ADDRCONF) + return (IPADM_INVALID_ARG); + ipaddr->ipadm_stateful = stateful; + + return (IPADM_SUCCESS); +} + +/* + * Sets the dhcp parameter `ipadm_primary' in the address object `ipaddr'. + * The field is used during the address creation with address + * type IPADM_ADDR_DHCP. It specifies if the interface should be set + * as a primary interface for getting dhcp global options from the DHCP server. + */ +ipadm_status_t +ipadm_set_primary(ipadm_addrobj_t ipaddr, boolean_t primary) +{ + if (ipaddr == NULL || ipaddr->ipadm_atype != IPADM_ADDR_DHCP) + return (IPADM_INVALID_ARG); + ipaddr->ipadm_primary = primary; + + return (IPADM_SUCCESS); +} + +/* + * Sets the dhcp parameter `ipadm_wait' in the address object `ipaddr'. + * This field is used during the address creation with address type + * IPADM_ADDR_DHCP. It specifies how long the API ipadm_create_addr() + * should wait before returning while the dhcp address is being acquired + * by the dhcpagent. + * Possible values: + * - IPADM_DHCP_WAIT_FOREVER : Do not return until dhcpagent returns. + * - IPADM_DHCP_WAIT_DEFAULT : Wait a default amount of time before returning. + * - <integer> : Wait the specified number of seconds before returning. + */ +ipadm_status_t +ipadm_set_wait_time(ipadm_addrobj_t ipaddr, int32_t wait) +{ + if (ipaddr == NULL || ipaddr->ipadm_atype != IPADM_ADDR_DHCP) + return (IPADM_INVALID_ARG); + ipaddr->ipadm_wait = wait; + return (IPADM_SUCCESS); +} + +/* + * Creates a placeholder for the `ipadm_aobjname' in the ipmgmtd `aobjmap'. + * If the `aobjname' already exists in the daemon's `aobjmap' then + * IPADM_ADDROBJ_EXISTS will be returned. + * + * If the libipadm consumer set `ipaddr.ipadm_aobjname[0]' to `\0', then the + * daemon will generate an `aobjname' for the given `ipaddr'. + */ +ipadm_status_t +i_ipadm_lookupadd_addrobj(ipadm_handle_t iph, ipadm_addrobj_t ipaddr) +{ + ipmgmt_aobjop_arg_t larg; + ipmgmt_aobjop_rval_t rval, *rvalp; + int err; + + bzero(&larg, sizeof (larg)); + larg.ia_cmd = IPMGMT_CMD_ADDROBJ_LOOKUPADD; + (void) strlcpy(larg.ia_aobjname, ipaddr->ipadm_aobjname, + sizeof (larg.ia_aobjname)); + (void) strlcpy(larg.ia_ifname, ipaddr->ipadm_ifname, + sizeof (larg.ia_ifname)); + larg.ia_family = ipaddr->ipadm_af; + + rvalp = &rval; + err = ipadm_door_call(iph, &larg, sizeof (larg), (void **)&rvalp, + sizeof (rval), B_FALSE); + if (err == 0 && ipaddr->ipadm_aobjname[0] == '\0') { + /* copy the daemon generated `aobjname' into `ipadddr' */ + (void) strlcpy(ipaddr->ipadm_aobjname, rval.ir_aobjname, + sizeof (ipaddr->ipadm_aobjname)); + } + if (err == EEXIST) + return (IPADM_ADDROBJ_EXISTS); + return (ipadm_errno2status(err)); +} + +/* + * Creates the IPv4 or IPv6 address in the nvlist `nvl' on the interface + * `ifname'. If a hostname is present, it is resolved before the address + * is created. + */ +ipadm_status_t +i_ipadm_enable_static(ipadm_handle_t iph, const char *ifname, nvlist_t *nvl, + sa_family_t af) +{ + char *prefixlenstr = NULL; + char *upstr = NULL; + char *sname = NULL, *dname = NULL; + struct ipadm_addrobj_s ipaddr; + char *aobjname = NULL; + nvlist_t *nvaddr = NULL; + nvpair_t *nvp; + char *cidraddr; + char *name; + ipadm_status_t status; + int err = 0; + uint32_t flags = IPADM_OPT_ACTIVE; + + /* retrieve the address information */ + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_IPV4ADDR) == 0 || + strcmp(name, IPADM_NVP_IPV6ADDR) == 0) { + err = nvpair_value_nvlist(nvp, &nvaddr); + } else if (strcmp(name, IPADM_NVP_AOBJNAME) == 0) { + err = nvpair_value_string(nvp, &aobjname); + } else if (strcmp(name, IPADM_NVP_PREFIXLEN) == 0) { + err = nvpair_value_string(nvp, &prefixlenstr); + } else if (strcmp(name, "up") == 0) { + err = nvpair_value_string(nvp, &upstr); + } + if (err != 0) + return (ipadm_errno2status(err)); + } + for (nvp = nvlist_next_nvpair(nvaddr, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvaddr, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_IPADDRHNAME) == 0) + err = nvpair_value_string(nvp, &sname); + else if (strcmp(name, IPADM_NVP_IPDADDRHNAME) == 0) + err = nvpair_value_string(nvp, &dname); + if (err != 0) + return (ipadm_errno2status(err)); + } + + if (strcmp(upstr, "yes") == 0) + flags |= IPADM_OPT_UP; + + /* build the address object from the above information */ + i_ipadm_init_addr(&ipaddr, ifname, aobjname, IPADM_ADDR_STATIC); + if (prefixlenstr != NULL && atoi(prefixlenstr) > 0) { + if (asprintf(&cidraddr, "%s/%s", sname, prefixlenstr) == -1) + return (IPADM_NO_MEMORY); + status = ipadm_set_addr(&ipaddr, cidraddr, af); + free(cidraddr); + } else { + status = ipadm_set_addr(&ipaddr, sname, af); + } + if (status != IPADM_SUCCESS) + return (status); + + if (dname != NULL) { + status = ipadm_set_dst_addr(&ipaddr, dname, af); + if (status != IPADM_SUCCESS) + return (status); + } + return (i_ipadm_create_addr(iph, &ipaddr, flags)); +} + +/* + * Creates a dhcp address on the interface `ifname' based on the + * IPADM_ADDR_DHCP address object parameters from the nvlist `nvl'. + */ +ipadm_status_t +i_ipadm_enable_dhcp(ipadm_handle_t iph, const char *ifname, nvlist_t *nvl) +{ + int32_t wait; + boolean_t primary; + nvlist_t *nvdhcp; + nvpair_t *nvp; + char *name; + struct ipadm_addrobj_s ipaddr; + char *aobjname; + int err = 0; + + /* Extract the dhcp parameters */ + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_DHCP) == 0) + err = nvpair_value_nvlist(nvp, &nvdhcp); + else if (strcmp(name, IPADM_NVP_AOBJNAME) == 0) + err = nvpair_value_string(nvp, &aobjname); + if (err != 0) + return (ipadm_errno2status(err)); + } + for (nvp = nvlist_next_nvpair(nvdhcp, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvdhcp, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_WAIT) == 0) + err = nvpair_value_int32(nvp, &wait); + else if (strcmp(name, IPADM_NVP_PRIMARY) == 0) + err = nvpair_value_boolean_value(nvp, &primary); + if (err != 0) + return (ipadm_errno2status(err)); + } + + /* Build the address object */ + i_ipadm_init_addr(&ipaddr, ifname, aobjname, IPADM_ADDR_DHCP); + ipaddr.ipadm_primary = primary; + if (iph->iph_flags & IPH_INIT) + ipaddr.ipadm_wait = 0; + else + ipaddr.ipadm_wait = wait; + return (i_ipadm_create_dhcp(iph, &ipaddr, IPADM_OPT_ACTIVE)); +} + +/* + * Creates auto-configured addresses on the interface `ifname' based on + * the IPADM_ADDR_IPV6_ADDRCONF address object parameters from the nvlist `nvl'. + */ +ipadm_status_t +i_ipadm_enable_addrconf(ipadm_handle_t iph, const char *ifname, nvlist_t *nvl) +{ + struct ipadm_addrobj_s ipaddr; + char *stateful = NULL, *stateless = NULL; + uint_t n; + uint8_t *addr6 = NULL; + uint32_t intfidlen = 0; + char *aobjname; + nvlist_t *nvaddr; + nvpair_t *nvp; + char *name; + int err = 0; + + /* Extract the parameters */ + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_INTFID) == 0) + err = nvpair_value_nvlist(nvp, &nvaddr); + else if (strcmp(name, IPADM_NVP_AOBJNAME) == 0) + err = nvpair_value_string(nvp, &aobjname); + if (err != 0) + return (ipadm_errno2status(err)); + } + for (nvp = nvlist_next_nvpair(nvaddr, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvaddr, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_IPNUMADDR) == 0) + err = nvpair_value_uint8_array(nvp, &addr6, &n); + if (strcmp(name, IPADM_NVP_PREFIXLEN) == 0) + err = nvpair_value_uint32(nvp, &intfidlen); + else if (strcmp(name, IPADM_NVP_STATELESS) == 0) + err = nvpair_value_string(nvp, &stateless); + else if (strcmp(name, IPADM_NVP_STATEFUL) == 0) + err = nvpair_value_string(nvp, &stateful); + if (err != 0) + return (ipadm_errno2status(err)); + } + /* Build the address object. */ + i_ipadm_init_addr(&ipaddr, ifname, aobjname, IPADM_ADDR_IPV6_ADDRCONF); + if (intfidlen > 0) { + ipaddr.ipadm_intfidlen = intfidlen; + bcopy(addr6, &ipaddr.ipadm_intfid.sin6_addr.s6_addr, n); + } + ipaddr.ipadm_stateless = (strcmp(stateless, "yes") == 0); + ipaddr.ipadm_stateful = (strcmp(stateful, "yes") == 0); + return (i_ipadm_create_ipv6addrs(iph, &ipaddr, IPADM_OPT_ACTIVE)); +} + +/* + * Allocates `ipadm_addrobj_t' and populates the relevant member fields based on + * the provided `type'. `aobjname' represents the address object name, which + * is of the form `<ifname>/<addressname>'. + * + * The caller has to minimally provide <ifname>. If <addressname> is not + * provided, then a default one will be generated by the API. + */ +ipadm_status_t +ipadm_create_addrobj(ipadm_addr_type_t type, const char *aobjname, + ipadm_addrobj_t *ipaddr) +{ + ipadm_addrobj_t newaddr; + ipadm_status_t status; + char *aname, *cp; + char ifname[IPADM_AOBJSIZ]; + ifspec_t ifsp; + + if (ipaddr == NULL) + return (IPADM_INVALID_ARG); + *ipaddr = NULL; + + if (aobjname == NULL || aobjname[0] == '\0') + return (IPADM_INVALID_ARG); + + if (strlcpy(ifname, aobjname, IPADM_AOBJSIZ) >= IPADM_AOBJSIZ) + return (IPADM_INVALID_ARG); + + if ((aname = strchr(ifname, '/')) != NULL) + *aname++ = '\0'; + + /* Check if the interface name is valid. */ + if (!ifparse_ifspec(ifname, &ifsp)) + return (IPADM_INVALID_ARG); + + /* Check if the given addrobj name is valid. */ + if (aname != NULL && !i_ipadm_is_user_aobjname_valid(aname)) + return (IPADM_INVALID_ARG); + + if ((newaddr = calloc(1, sizeof (struct ipadm_addrobj_s))) == NULL) + return (IPADM_NO_MEMORY); + + /* + * If the ifname has logical interface number, extract it and assign + * it to `ipadm_lifnum'. Only applications with IPH_LEGACY set will do + * this today. We will check for the validity later in + * i_ipadm_validate_create_addr(). + */ + if (ifsp.ifsp_lunvalid) { + newaddr->ipadm_lifnum = ifsp.ifsp_lun; + cp = strchr(ifname, IPADM_LOGICAL_SEP); + *cp = '\0'; + } + (void) strlcpy(newaddr->ipadm_ifname, ifname, + sizeof (newaddr->ipadm_ifname)); + + if (aname != NULL) { + (void) snprintf(newaddr->ipadm_aobjname, + sizeof (newaddr->ipadm_aobjname), "%s/%s", ifname, aname); + } + + switch (type) { + case IPADM_ADDR_IPV6_ADDRCONF: + newaddr->ipadm_intfidlen = 0; + newaddr->ipadm_stateful = B_TRUE; + newaddr->ipadm_stateless = B_TRUE; + newaddr->ipadm_af = AF_INET6; + break; + + case IPADM_ADDR_DHCP: + newaddr->ipadm_primary = B_FALSE; + newaddr->ipadm_wait = IPADM_DHCP_WAIT_DEFAULT; + newaddr->ipadm_af = AF_INET; + break; + + case IPADM_ADDR_STATIC: + newaddr->ipadm_af = AF_UNSPEC; + newaddr->ipadm_static_prefixlen = 0; + break; + default: + status = IPADM_INVALID_ARG; + goto fail; + } + newaddr->ipadm_atype = type; + *ipaddr = newaddr; + return (IPADM_SUCCESS); +fail: + free(newaddr); + return (status); +} + +/* + * Frees the address object in `ipaddr'. + */ +void +ipadm_destroy_addrobj(ipadm_addrobj_t ipaddr) +{ + free(ipaddr); +} + +/* + * Retrieves the logical interface name from `ipaddr' and stores the + * string in `lifname'. + */ +void +i_ipadm_addrobj2lifname(ipadm_addrobj_t ipaddr, char *lifname, int lifnamesize) +{ + if (ipaddr->ipadm_lifnum != 0) { + (void) snprintf(lifname, lifnamesize, "%s:%d", + ipaddr->ipadm_ifname, ipaddr->ipadm_lifnum); + } else { + (void) snprintf(lifname, lifnamesize, "%s", + ipaddr->ipadm_ifname); + } +} + +/* + * Checks if a non-zero static address is present on the 0th logical interface + * of the given IPv4 or IPv6 physical interface. For an IPv4 interface, it + * also checks if the interface is under DHCP control. If the condition is true, + * the output argument `exists' will be set to B_TRUE. Otherwise, `exists' + * is set to B_FALSE. + */ +static ipadm_status_t +i_ipadm_addr_exists_on_if(ipadm_handle_t iph, const char *ifname, + sa_family_t af, boolean_t *exists) +{ + struct lifreq lifr; + int sock; + + /* For IPH_LEGACY, a new logical interface will never be added. */ + if (iph->iph_flags & IPH_LEGACY) { + *exists = B_FALSE; + return (IPADM_SUCCESS); + } + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (af == AF_INET) { + sock = iph->iph_sock; + if (ioctl(sock, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + if (lifr.lifr_flags & IFF_DHCPRUNNING) { + *exists = B_TRUE; + return (IPADM_SUCCESS); + } + } else { + sock = iph->iph_sock6; + } + if (ioctl(sock, SIOCGLIFADDR, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + *exists = !sockaddrunspec(&lifr.lifr_addr); + + return (IPADM_SUCCESS); +} + +/* + * Reads all the address lines from the persistent DB into the nvlist `onvl', + * when both `ifname' and `aobjname' are NULL. If an `ifname' is provided, + * it returns all the addresses for the given interface `ifname'. + * If an `aobjname' is specified, then the address line corresponding to + * that name will be returned. + */ +static ipadm_status_t +i_ipadm_get_db_addr(ipadm_handle_t iph, const char *ifname, + const char *aobjname, nvlist_t **onvl) +{ + ipmgmt_getaddr_arg_t garg; + ipmgmt_get_rval_t *rvalp; + int err; + size_t nvlsize; + char *nvlbuf; + + /* Populate the door_call argument structure */ + bzero(&garg, sizeof (garg)); + garg.ia_cmd = IPMGMT_CMD_GETADDR; + if (aobjname != NULL) + (void) strlcpy(garg.ia_aobjname, aobjname, + sizeof (garg.ia_aobjname)); + if (ifname != NULL) + (void) strlcpy(garg.ia_ifname, ifname, sizeof (garg.ia_ifname)); + + rvalp = malloc(sizeof (ipmgmt_get_rval_t)); + err = ipadm_door_call(iph, &garg, sizeof (garg), (void **)&rvalp, + sizeof (*rvalp), B_TRUE); + if (err == 0) { + nvlsize = rvalp->ir_nvlsize; + nvlbuf = (char *)rvalp + sizeof (ipmgmt_get_rval_t); + err = nvlist_unpack(nvlbuf, nvlsize, onvl, NV_ENCODE_NATIVE); + } + free(rvalp); + return (ipadm_errno2status(err)); +} + +/* + * Adds the IP address contained in the 'ipaddr' argument to the physical + * interface represented by 'ifname' after doing the required validation. + * If the interface does not exist, it is created before the address is + * added. + * + * If IPH_LEGACY is set in iph_flags, flags has to be IPADM_OPT_ACTIVE + * and a default addrobj name will be generated. Input `addr->ipadm_aobjname', + * if provided, will be ignored and replaced with the newly generated name. + * The interface name provided has to be a logical interface name that + * already exists. No new logical interface will be added in this function. + */ +ipadm_status_t +ipadm_create_addr(ipadm_handle_t iph, ipadm_addrobj_t addr, uint32_t flags) +{ + ipadm_status_t status; + sa_family_t af; + sa_family_t daf; + sa_family_t other_af; + boolean_t created_af = B_FALSE; + boolean_t created_other_af = B_FALSE; + ipadm_addr_type_t type; + char *ifname = addr->ipadm_ifname; + boolean_t legacy = (iph->iph_flags & IPH_LEGACY); + boolean_t aobjfound; + boolean_t is_6to4; + struct lifreq lifr; + uint64_t ifflags; + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + + /* Validate the addrobj. This also fills in addr->ipadm_ifname. */ + status = i_ipadm_validate_create_addr(iph, addr, flags); + if (status != IPADM_SUCCESS) + return (status); + + /* + * For Legacy case, check if an addrobj already exists for the + * given logical interface name. If one does not exist, + * a default name will be generated and added to the daemon's + * aobjmap. + */ + if (legacy) { + struct ipadm_addrobj_s ipaddr; + + ipaddr = *addr; + status = i_ipadm_get_lif2addrobj(iph, &ipaddr); + if (status == IPADM_SUCCESS) { + aobjfound = B_TRUE; + /* + * With IPH_LEGACY, modifying an address that is not + * a static address will return with an error. + */ + if (ipaddr.ipadm_atype != IPADM_ADDR_STATIC) + return (IPADM_NOTSUP); + /* + * we found the addrobj in daemon, copy over the + * aobjname to `addr'. + */ + (void) strlcpy(addr->ipadm_aobjname, + ipaddr.ipadm_aobjname, IPADM_AOBJSIZ); + } else if (status == IPADM_NOTFOUND) { + aobjfound = B_FALSE; + } else { + return (status); + } + } + + af = addr->ipadm_af; + /* + * Create a placeholder for this address object in the daemon. + * Skip this step for IPH_LEGACY case if the addrobj already + * exists. + */ + if (!legacy || !aobjfound) { + status = i_ipadm_lookupadd_addrobj(iph, addr); + if (status != IPADM_SUCCESS) + return (status); + } + + is_6to4 = i_ipadm_is_6to4(iph, ifname); + /* Plumb the IP interfaces if necessary */ + status = i_ipadm_create_if(iph, ifname, af, flags); + if (status != IPADM_SUCCESS && status != IPADM_IF_EXISTS) { + (void) i_ipadm_delete_addrobj(iph, addr, IPADM_OPT_ACTIVE); + return (status); + } + if (status == IPADM_SUCCESS) + created_af = B_TRUE; + if (!is_6to4 && !legacy) { + other_af = (af == AF_INET ? AF_INET6 : AF_INET); + status = i_ipadm_create_if(iph, ifname, other_af, flags); + if (status != IPADM_SUCCESS && status != IPADM_IF_EXISTS) { + (void) i_ipadm_delete_if(iph, ifname, af, flags); + return (status); + } + if (status == IPADM_SUCCESS) + created_other_af = B_TRUE; + } + + /* Validate static addresses for IFF_POINTOPOINT interfaces. */ + if (addr->ipadm_atype == IPADM_ADDR_STATIC) { + status = i_ipadm_get_flags(iph, ifname, af, &ifflags); + if (status != IPADM_SUCCESS) + goto fail; + daf = addr->ipadm_static_dst_addr.ss_family; + if (ifflags & IFF_POINTOPOINT) { + if (is_6to4) { + if (af != AF_INET6 || daf != AF_UNSPEC) { + status = IPADM_INVALID_ARG; + goto fail; + } + } else { + if (daf != af) { + status = IPADM_INVALID_ARG; + goto fail; + } + /* Check for a valid dst address. */ + if (!legacy && sockaddrunspec( + &addr->ipadm_static_dst_addr)) { + status = IPADM_BAD_ADDR; + goto fail; + } + } + } else { + /* + * Disallow setting of dstaddr when the link is not + * a point-to-point link. + */ + if (daf != AF_UNSPEC) + return (IPADM_INVALID_ARG); + } + } + + /* + * For 6to4 interfaces, kernel configures a default link-local + * address. We need to replace it, if the caller has provided + * an address that is different from the default link-local. + */ + if (status == IPADM_SUCCESS && is_6to4) { + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, addr->ipadm_ifname, + sizeof (lifr.lifr_name)); + if (ioctl(iph->iph_sock6, SIOCGLIFADDR, &lifr) < 0) { + status = ipadm_errno2status(errno); + goto fail; + } + if (sockaddrcmp(&lifr.lifr_addr, &addr->ipadm_static_addr)) + return (IPADM_SUCCESS); + } + + /* Create the address. */ + type = addr->ipadm_atype; + switch (type) { + case IPADM_ADDR_STATIC: + status = i_ipadm_create_addr(iph, addr, flags); + break; + case IPADM_ADDR_DHCP: + status = i_ipadm_create_dhcp(iph, addr, flags); + break; + case IPADM_ADDR_IPV6_ADDRCONF: + status = i_ipadm_create_ipv6addrs(iph, addr, flags); + break; + default: + status = IPADM_INVALID_ARG; + break; + } + + /* + * If address was not created successfully, unplumb the interface + * if it was plumbed implicitly in this function and remove the + * addrobj created by the ipmgmtd daemon as a placeholder. + * If IPH_LEGACY is set, then remove the addrobj only if it was + * created in this function. + */ +fail: + if (status != IPADM_DHCP_IPC_TIMEOUT && + status != IPADM_SUCCESS) { + if (!legacy) { + if (created_af || created_other_af) { + if (created_af) { + (void) i_ipadm_delete_if(iph, ifname, + af, flags); + } + if (created_other_af) { + (void) i_ipadm_delete_if(iph, ifname, + other_af, flags); + } + } else { + (void) i_ipadm_delete_addrobj(iph, addr, flags); + } + } else if (!aobjfound) { + (void) i_ipadm_delete_addrobj(iph, addr, flags); + } + } + + return (status); +} + +/* + * Creates the static address in `ipaddr' in kernel. After successfully + * creating it, it updates the ipmgmtd daemon's aobjmap with the logical + * interface information. + */ +static ipadm_status_t +i_ipadm_create_addr(ipadm_handle_t iph, ipadm_addrobj_t ipaddr, uint32_t flags) +{ + struct lifreq lifr; + ipadm_status_t status = IPADM_SUCCESS; + int sock; + struct sockaddr_storage m, *mask = &m; + const struct sockaddr_storage *addr = &ipaddr->ipadm_static_addr; + const struct sockaddr_storage *daddr = &ipaddr->ipadm_static_dst_addr; + sa_family_t af; + boolean_t addif; + boolean_t legacy = (iph->iph_flags & IPH_LEGACY); + struct ipadm_addrobj_s legacy_addr; + boolean_t default_prefixlen = B_FALSE; + + af = ipaddr->ipadm_af; + sock = (af == AF_INET ? iph->iph_sock : iph->iph_sock6); + + /* If prefixlen was not provided, get default prefixlen */ + if (ipaddr->ipadm_static_prefixlen == 0) { + /* prefixlen was not provided, get default prefixlen */ + status = i_ipadm_get_default_prefixlen( + &ipaddr->ipadm_static_addr, + &ipaddr->ipadm_static_prefixlen); + if (status != IPADM_SUCCESS) + return (status); + default_prefixlen = B_TRUE; + } + (void) plen2mask(ipaddr->ipadm_static_prefixlen, af, mask); + + /* + * Check if the interface already has a non-zero address on 0th lif. + * It it does, we create a new logical interface and add the address + * on the new logical interface. If not, we replace the zero address + * on 0th logical interface with the given address. + */ + status = i_ipadm_addr_exists_on_if(iph, ipaddr->ipadm_ifname, af, + &addif); + if (status != IPADM_SUCCESS) + return (status); + /* + * This is a "hack" to get around the problem of SIOCLIFADDIF. The + * problem is that this ioctl does not include the netmask when adding + * a logical interface. + * + * To get around this problem, we first add the logical interface with + * a 0 address. After that, we set the netmask if provided. Finally + * we set the interface address. + */ + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ipaddr->ipadm_ifname, + sizeof (lifr.lifr_name)); + if (addif) { + if (ioctl(sock, SIOCLIFADDIF, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + ipaddr->ipadm_lifnum = i_ipadm_get_lnum(lifr.lifr_name); + } else if (!legacy) { + /* + * If IPH_LEGACY is specified, the input logical interface + * number is already available in ipadm_lifnum. + */ + ipaddr->ipadm_lifnum = 0; + } + i_ipadm_addrobj2lifname(ipaddr, lifr.lifr_name, + sizeof (lifr.lifr_name)); + lifr.lifr_addr = *mask; + if (ioctl(sock, SIOCSLIFNETMASK, (caddr_t)&lifr) < 0) { + status = ipadm_errno2status(errno); + goto ret; + } + lifr.lifr_addr = *addr; + if (ioctl(sock, SIOCSLIFADDR, (caddr_t)&lifr) < 0) { + status = ipadm_errno2status(errno); + goto ret; + } + /* Set the destination address, if one is given. */ + if (daddr->ss_family != AF_UNSPEC) { + lifr.lifr_addr = *daddr; + if (ioctl(sock, SIOCSLIFDSTADDR, (caddr_t)&lifr) < 0) { + status = ipadm_errno2status(errno); + goto ret; + } + } + + if (flags & IPADM_OPT_UP) { + status = i_ipadm_set_flags(iph, lifr.lifr_name, af, IFF_UP, 0); + + /* + * IPADM_DAD_FOUND is a soft-error for create-addr. + * No need to tear down the address. + */ + if (status == IPADM_DAD_FOUND) + status = IPADM_SUCCESS; + } + + if (status == IPADM_SUCCESS) { + /* + * For IPH_LEGACY, we might be modifying the address on + * an address object that already exists e.g. by doing + * "ifconfig bge0:1 <addr>; ifconfig bge0:1 <newaddr>" + * So, we need to store the object only if it does not + * already exist in ipmgmtd. + */ + if (legacy) { + bzero(&legacy_addr, sizeof (legacy_addr)); + (void) strlcpy(legacy_addr.ipadm_aobjname, + ipaddr->ipadm_aobjname, + sizeof (legacy_addr.ipadm_aobjname)); + status = i_ipadm_get_addrobj(iph, &legacy_addr); + if (status == IPADM_SUCCESS && + legacy_addr.ipadm_lifnum >= 0) { + return (status); + } + } + status = i_ipadm_addr_persist(iph, ipaddr, default_prefixlen, + flags); + } +ret: + if (status != IPADM_SUCCESS && !legacy) + (void) i_ipadm_delete_addr(iph, ipaddr); + return (status); +} + +/* + * Removes the address object identified by `aobjname' from both active and + * persistent configuration. The address object will be removed from only + * active configuration if IPH_LEGACY is set in `iph->iph_flags'. + * + * If the address type is IPADM_ADDR_STATIC or IPADM_ADDR_DHCP, the address + * in the address object will be removed from the physical interface. + * If the address type is IPADM_ADDR_DHCP, the flag IPADM_OPT_RELEASE specifies + * whether the lease should be released. If IPADM_OPT_RELEASE is not + * specified, the lease will be dropped. This option is ignored + * for other address types. + * + * If the address type is IPADM_ADDR_IPV6_ADDRCONF, the link-local address and + * all the autoconfigured addresses will be removed. + * Finally, the address object is also removed from ipmgmtd's aobjmap and from + * the persistent DB. + */ +ipadm_status_t +ipadm_delete_addr(ipadm_handle_t iph, const char *aobjname, uint32_t flags) +{ + ipadm_status_t status; + struct ipadm_addrobj_s ipaddr; + boolean_t release = ((flags & IPADM_OPT_RELEASE) != 0); + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + + /* validate input */ + if (flags == 0 || ((flags & IPADM_OPT_PERSIST) && + !(flags & IPADM_OPT_ACTIVE)) || + (flags & ~(IPADM_COMMON_OPT_MASK|IPADM_OPT_RELEASE))) { + return (IPADM_INVALID_ARG); + } + bzero(&ipaddr, sizeof (ipaddr)); + if (aobjname == NULL || strlcpy(ipaddr.ipadm_aobjname, aobjname, + IPADM_AOBJSIZ) >= IPADM_AOBJSIZ) { + return (IPADM_INVALID_ARG); + } + + /* Retrieve the address object information from ipmgmtd. */ + status = i_ipadm_get_addrobj(iph, &ipaddr); + if (status != IPADM_SUCCESS) + return (status); + + if (release && ipaddr.ipadm_atype != IPADM_ADDR_DHCP) + return (IPADM_NOTSUP); + /* + * If requested to delete just from active config but the address + * is not in active config, return error. + */ + if (!(ipaddr.ipadm_flags & IPMGMT_ACTIVE) && + (flags & IPADM_OPT_ACTIVE) && !(flags & IPADM_OPT_PERSIST)) { + return (IPADM_NOTFOUND); + } + + /* + * If address is present in active config, remove it from + * kernel. + */ + if (ipaddr.ipadm_flags & IPMGMT_ACTIVE) { + switch (ipaddr.ipadm_atype) { + case IPADM_ADDR_STATIC: + status = i_ipadm_delete_addr(iph, &ipaddr); + break; + case IPADM_ADDR_DHCP: + status = i_ipadm_delete_dhcp(iph, &ipaddr, release); + break; + case IPADM_ADDR_IPV6_ADDRCONF: + status = i_ipadm_delete_ipv6addrs(iph, &ipaddr); + break; + default: + /* + * This is the case of address object name residing in + * daemon's aobjmap (added by ADDROBJ_LOOKUPADD). Fall + * through and delete that address object. + */ + break; + } + + /* + * If the address was previously deleted from the active + * config, we will get a IPADM_ENXIO from kernel. + * We will still proceed and purge the address information + * in the DB. + */ + if (status == IPADM_ENXIO) + status = IPADM_SUCCESS; + else if (status != IPADM_SUCCESS) + return (status); + } + + if (!(ipaddr.ipadm_flags & IPMGMT_PERSIST) && + (flags & IPADM_OPT_PERSIST)) { + flags &= ~IPADM_OPT_PERSIST; + } + status = i_ipadm_delete_addrobj(iph, &ipaddr, flags); + if (status == IPADM_NOTFOUND) + return (status); + return (IPADM_SUCCESS); +} + +/* + * Starts the dhcpagent and sends it the message DHCP_START to start + * configuring a dhcp address on the given interface in `addr'. + * After making the dhcpagent request, it also updates the + * address object information in ipmgmtd's aobjmap and creates an + * entry in persistent DB if IPADM_OPT_PERSIST is set in `flags'. + */ +static ipadm_status_t +i_ipadm_create_dhcp(ipadm_handle_t iph, ipadm_addrobj_t addr, uint32_t flags) +{ + struct lifreq lifr; + int sock = iph->iph_sock; + ipadm_status_t status; + ipadm_status_t dh_status; + boolean_t addif; + + if (dhcp_start_agent(DHCP_IPC_MAX_WAIT) == -1) + return (IPADM_DHCP_START_ERROR); + /* + * Check if a new logical interface has to be created. + */ + addr->ipadm_lifnum = 0; + status = i_ipadm_addr_exists_on_if(iph, addr->ipadm_ifname, AF_INET, + &addif); + if (status != IPADM_SUCCESS) + return (status); + if (addif) { + /* + * If there is an address on 0th logical interface, + * add a new logical interface. + */ + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, addr->ipadm_ifname, + sizeof (lifr.lifr_name)); + if (ioctl(sock, SIOCLIFADDIF, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + addr->ipadm_lifnum = i_ipadm_get_lnum(lifr.lifr_name); + } + /* Send DHCP_START to the dhcpagent. */ + status = i_ipadm_op_dhcp(addr, DHCP_START, NULL); + /* + * We do not undo the create-addr operation for IPADM_DHCP_IPC_TIMEOUT + * since it is only a soft error to indicate the caller that the lease + * might be required after the function returns. + */ + if (status != IPADM_SUCCESS && status != IPADM_DHCP_IPC_TIMEOUT) + goto fail; + dh_status = status; + + /* Persist the address object information in ipmgmtd. */ + status = i_ipadm_addr_persist(iph, addr, B_FALSE, flags); + if (status != IPADM_SUCCESS) + goto fail; + + return (dh_status); +fail: + /* In case of error, delete the dhcp address */ + (void) i_ipadm_delete_dhcp(iph, addr, B_TRUE); + return (status); +} + +/* + * Releases/drops the dhcp lease on the logical interface in the address + * object `addr'. If `release' is set to B_FALSE, the lease will be dropped. + */ +static ipadm_status_t +i_ipadm_delete_dhcp(ipadm_handle_t iph, ipadm_addrobj_t addr, boolean_t release) +{ + ipadm_status_t status; + int dherr; + + /* Send DHCP_RELEASE or DHCP_DROP to the dhcpagent */ + if (release) { + status = i_ipadm_op_dhcp(addr, DHCP_RELEASE, &dherr); + /* + * If no lease was obtained on the object, we should + * drop the dhcp control on the interface. + */ + if (status != IPADM_SUCCESS && dherr == DHCP_IPC_E_OUTSTATE) + status = i_ipadm_op_dhcp(addr, DHCP_DROP, NULL); + } else { + status = i_ipadm_op_dhcp(addr, DHCP_DROP, NULL); + } + if (status != IPADM_SUCCESS) + return (status); + + /* Delete the logical interface */ + if (addr->ipadm_lifnum != 0) { + struct lifreq lifr; + + bzero(&lifr, sizeof (lifr)); + i_ipadm_addrobj2lifname(addr, lifr.lifr_name, + sizeof (lifr.lifr_name)); + if (ioctl(iph->iph_sock, SIOCLIFREMOVEIF, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + } + + return (IPADM_SUCCESS); +} + +/* + * Communicates with the dhcpagent to send a dhcp message of type `type'. + * It returns the dhcp error in `dhcperror' if a non-null pointer is provided + * in `dhcperror'. + */ +static ipadm_status_t +i_ipadm_op_dhcp(ipadm_addrobj_t addr, dhcp_ipc_type_t type, int *dhcperror) +{ + dhcp_ipc_request_t *request; + dhcp_ipc_reply_t *reply = NULL; + char ifname[LIFNAMSIZ]; + int error; + int dhcp_timeout; + + /* Construct a message to the dhcpagent. */ + bzero(&ifname, sizeof (ifname)); + i_ipadm_addrobj2lifname(addr, ifname, sizeof (ifname)); + if (addr->ipadm_primary) + type |= DHCP_PRIMARY; + request = dhcp_ipc_alloc_request(type, ifname, NULL, 0, DHCP_TYPE_NONE); + if (request == NULL) + return (IPADM_NO_MEMORY); + + if (addr->ipadm_wait == IPADM_DHCP_WAIT_FOREVER) + dhcp_timeout = DHCP_IPC_WAIT_FOREVER; + else if (addr->ipadm_wait == IPADM_DHCP_WAIT_DEFAULT) + dhcp_timeout = DHCP_IPC_WAIT_DEFAULT; + else + dhcp_timeout = addr->ipadm_wait; + /* Send the message to dhcpagent. */ + error = dhcp_ipc_make_request(request, &reply, dhcp_timeout); + free(request); + if (error == 0) { + error = reply->return_code; + free(reply); + } + if (error != 0) { + if (dhcperror != NULL) + *dhcperror = error; + if (error != DHCP_IPC_E_TIMEOUT) + return (IPADM_DHCP_IPC_ERROR); + else if (dhcp_timeout != 0) + return (IPADM_DHCP_IPC_TIMEOUT); + } + + return (IPADM_SUCCESS); +} + +/* + * Returns the IP addresses of the specified interface in both the + * active and the persistent configuration. If no + * interface is specified, it returns all non-zero IP addresses + * configured on all interfaces in active and persistent + * configurations. + * `addrinfo' will contain addresses that are + * (1) in both active and persistent configuration (created persistently) + * (2) only in active configuration (created temporarily) + * (3) only in persistent configuration (disabled addresses) + * + * Address list that is returned by this function must be freed + * using the ipadm_freeaddr_info() function. + */ +ipadm_status_t +ipadm_addr_info(ipadm_handle_t iph, const char *ifname, + ipadm_addr_info_t **addrinfo, uint32_t flags, int64_t lifc_flags) +{ + ifspec_t ifsp; + + if (addrinfo == NULL || iph == NULL) + return (IPADM_INVALID_ARG); + if (ifname != NULL && + (!ifparse_ifspec(ifname, &ifsp) || ifsp.ifsp_lunvalid)) { + return (IPADM_INVALID_ARG); + } + return (i_ipadm_get_all_addr_info(iph, ifname, addrinfo, + flags, lifc_flags)); +} + +/* + * Frees the structure allocated by ipadm_addr_info(). + */ +void +ipadm_free_addr_info(ipadm_addr_info_t *ainfo) +{ + freeifaddrs((struct ifaddrs *)ainfo); +} + +/* + * Makes a door call to ipmgmtd to update its `aobjmap' with the address + * object in `ipaddr'. This door call also updates the persistent DB to + * remember address object to be recreated on next reboot or on an + * ipadm_enable_addr()/ipadm_enable_if() call. + */ +ipadm_status_t +i_ipadm_addr_persist(ipadm_handle_t iph, const ipadm_addrobj_t ipaddr, + boolean_t default_prefixlen, uint32_t flags) +{ + char *aname = ipaddr->ipadm_aobjname; + nvlist_t *nvl; + int err = 0; + ipadm_status_t status; + char pval[MAXPROPVALLEN]; + uint_t pflags = 0; + ipadm_prop_desc_t *pdp = NULL; + + /* + * Construct the nvl to send to the door. + */ + if (nvlist_alloc(&nvl, NV_UNIQUE_NAME, 0) != 0) + return (IPADM_NO_MEMORY); + if ((err = nvlist_add_string(nvl, IPADM_NVP_IFNAME, + ipaddr->ipadm_ifname)) != 0 || + (err = nvlist_add_string(nvl, IPADM_NVP_AOBJNAME, aname)) != 0 || + (err = nvlist_add_int32(nvl, IPADM_NVP_LIFNUM, + ipaddr->ipadm_lifnum)) != 0) { + status = ipadm_errno2status(err); + goto ret; + } + switch (ipaddr->ipadm_atype) { + case IPADM_ADDR_STATIC: + status = i_ipadm_add_ipaddr2nvl(nvl, ipaddr); + if (status != IPADM_SUCCESS) + goto ret; + (void) snprintf(pval, sizeof (pval), "%d", + ipaddr->ipadm_static_prefixlen); + if (flags & IPADM_OPT_UP) + err = nvlist_add_string(nvl, "up", "yes"); + else + err = nvlist_add_string(nvl, "up", "no"); + status = ipadm_errno2status(err); + break; + case IPADM_ADDR_DHCP: + status = i_ipadm_add_dhcp2nvl(nvl, ipaddr->ipadm_primary, + ipaddr->ipadm_wait); + break; + case IPADM_ADDR_IPV6_ADDRCONF: + status = i_ipadm_add_intfid2nvl(nvl, ipaddr); + break; + } + if (status != IPADM_SUCCESS) + goto ret; + + if (iph->iph_flags & IPH_INIT) { + /* + * IPMGMT_INIT tells the ipmgmtd to set both IPMGMT_ACTIVE and + * IPMGMT_PERSIST on the address object in its `aobjmap'. + * For the callers ipadm_enable_if() and ipadm_enable_addr(), + * IPADM_OPT_PERSIST is not set in their flags. They send + * IPH_INIT in iph_flags, so that the address object will be + * set as both IPMGMT_ACTIVE and IPMGMT_PERSIST. + */ + pflags |= IPMGMT_INIT; + } else { + if (flags & IPADM_OPT_ACTIVE) + pflags |= IPMGMT_ACTIVE; + if (flags & IPADM_OPT_PERSIST) + pflags |= IPMGMT_PERSIST; + } + status = i_ipadm_addr_persist_nvl(iph, nvl, pflags); + /* + * prefixlen is stored in a separate line in the DB and not along + * with the address itself, since it is also an address property and + * all address properties are stored in separate lines. We need to + * persist the prefixlen by calling the function that persists + * address properties. + */ + if (status == IPADM_SUCCESS && !default_prefixlen && + ipaddr->ipadm_atype == IPADM_ADDR_STATIC && + (flags & IPADM_OPT_PERSIST)) { + for (pdp = ipadm_addrprop_table; pdp->ipd_name != NULL; pdp++) { + if (strcmp("prefixlen", pdp->ipd_name) == 0) + break; + } + assert(pdp != NULL); + status = i_ipadm_persist_propval(iph, pdp, pval, ipaddr, flags); + } +ret: + nvlist_free(nvl); + return (status); +} + +/* + * Makes the door call to ipmgmtd to store the address object in the + * nvlist `nvl'. + */ +static ipadm_status_t +i_ipadm_addr_persist_nvl(ipadm_handle_t iph, nvlist_t *nvl, uint32_t flags) +{ + char *buf = NULL, *nvlbuf = NULL; + size_t nvlsize, bufsize; + ipmgmt_setaddr_arg_t *sargp; + int err; + + err = nvlist_pack(nvl, &nvlbuf, &nvlsize, NV_ENCODE_NATIVE, 0); + if (err != 0) + return (ipadm_errno2status(err)); + bufsize = sizeof (*sargp) + nvlsize; + buf = calloc(1, bufsize); + sargp = (void *)buf; + sargp->ia_cmd = IPMGMT_CMD_SETADDR; + sargp->ia_flags = flags; + sargp->ia_nvlsize = nvlsize; + (void) bcopy(nvlbuf, buf + sizeof (*sargp), nvlsize); + err = ipadm_door_call(iph, buf, bufsize, NULL, 0, B_FALSE); + free(buf); + free(nvlbuf); + return (ipadm_errno2status(err)); +} + +/* + * Makes a door call to ipmgmtd to remove the address object in `ipaddr' + * from its `aobjmap'. This door call also removes the address object and all + * its properties from the persistent DB if IPADM_OPT_PERSIST is set in + * `flags', so that the object will not be recreated on next reboot or on an + * ipadm_enable_addr()/ipadm_enable_if() call. + */ +ipadm_status_t +i_ipadm_delete_addrobj(ipadm_handle_t iph, const ipadm_addrobj_t ipaddr, + uint32_t flags) +{ + ipmgmt_addr_arg_t arg; + int err; + + arg.ia_cmd = IPMGMT_CMD_RESETADDR; + arg.ia_flags = 0; + if (flags & IPADM_OPT_ACTIVE) + arg.ia_flags |= IPMGMT_ACTIVE; + if (flags & IPADM_OPT_PERSIST) + arg.ia_flags |= IPMGMT_PERSIST; + (void) strlcpy(arg.ia_aobjname, ipaddr->ipadm_aobjname, + sizeof (arg.ia_aobjname)); + arg.ia_lnum = ipaddr->ipadm_lifnum; + err = ipadm_door_call(iph, &arg, sizeof (arg), NULL, 0, B_FALSE); + return (ipadm_errno2status(err)); +} + +/* + * Checks if the caller is authorized for the up/down operation. + * Retrieves the address object corresponding to `aobjname' from ipmgmtd + * and retrieves the address flags for that object from kernel. + * The arguments `ipaddr' and `ifflags' must be allocated by the caller. + */ +static ipadm_status_t +i_ipadm_updown_common(ipadm_handle_t iph, const char *aobjname, + ipadm_addrobj_t ipaddr, uint32_t ipadm_flags, uint64_t *ifflags) +{ + ipadm_status_t status; + char lifname[LIFNAMSIZ]; + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + + /* validate input */ + if (aobjname == NULL || strlcpy(ipaddr->ipadm_aobjname, aobjname, + IPADM_AOBJSIZ) >= IPADM_AOBJSIZ) { + return (IPADM_INVALID_ARG); + } + + /* Retrieve the address object information. */ + status = i_ipadm_get_addrobj(iph, ipaddr); + if (status != IPADM_SUCCESS) + return (status); + + if (!(ipaddr->ipadm_flags & IPMGMT_ACTIVE)) + return (IPADM_OP_DISABLE_OBJ); + if ((ipadm_flags & IPADM_OPT_PERSIST) && + !(ipaddr->ipadm_flags & IPMGMT_PERSIST)) + return (IPADM_TEMPORARY_OBJ); + if (ipaddr->ipadm_atype == IPADM_ADDR_IPV6_ADDRCONF || + (ipaddr->ipadm_atype == IPADM_ADDR_DHCP && + (ipadm_flags & IPADM_OPT_PERSIST))) + return (IPADM_NOTSUP); + + i_ipadm_addrobj2lifname(ipaddr, lifname, sizeof (lifname)); + return (i_ipadm_get_flags(iph, lifname, ipaddr->ipadm_af, ifflags)); +} + +/* + * Marks the address in the address object `aobjname' up. This operation is + * not supported for an address object of type IPADM_ADDR_IPV6_ADDRCONF. + * For an address object of type IPADM_ADDR_DHCP, this operation can + * only be temporary and no updates will be made to the persistent DB. + */ +ipadm_status_t +ipadm_up_addr(ipadm_handle_t iph, const char *aobjname, uint32_t ipadm_flags) +{ + struct ipadm_addrobj_s ipaddr; + ipadm_status_t status; + uint64_t flags; + char lifname[LIFNAMSIZ]; + + status = i_ipadm_updown_common(iph, aobjname, &ipaddr, ipadm_flags, + &flags); + if (status != IPADM_SUCCESS) + return (status); + if (flags & IFF_UP) + goto persist; + /* + * If the address is already a duplicate, then refresh-addr + * should be used to mark it up. + */ + if (flags & IFF_DUPLICATE) + return (IPADM_DAD_FOUND); + + i_ipadm_addrobj2lifname(&ipaddr, lifname, sizeof (lifname)); + status = i_ipadm_set_flags(iph, lifname, ipaddr.ipadm_af, IFF_UP, 0); + if (status != IPADM_SUCCESS) + return (status); + +persist: + /* Update persistent DB. */ + if (ipadm_flags & IPADM_OPT_PERSIST) { + status = i_ipadm_persist_propval(iph, &up_addrprop, + "yes", &ipaddr, 0); + } + + return (status); +} + +/* + * Marks the address in the address object `aobjname' down. This operation is + * not supported for an address object of type IPADM_ADDR_IPV6_ADDRCONF. + * For an address object of type IPADM_ADDR_DHCP, this operation can + * only be temporary and no updates will be made to the persistent DB. + */ +ipadm_status_t +ipadm_down_addr(ipadm_handle_t iph, const char *aobjname, uint32_t ipadm_flags) +{ + struct ipadm_addrobj_s ipaddr; + ipadm_status_t status; + struct lifreq lifr; + uint64_t flags; + + status = i_ipadm_updown_common(iph, aobjname, &ipaddr, ipadm_flags, + &flags); + if (status != IPADM_SUCCESS) + return (status); + i_ipadm_addrobj2lifname(&ipaddr, lifr.lifr_name, + sizeof (lifr.lifr_name)); + if (flags & IFF_UP) { + status = i_ipadm_set_flags(iph, lifr.lifr_name, + ipaddr.ipadm_af, 0, IFF_UP); + if (status != IPADM_SUCCESS) + return (status); + } else if (flags & IFF_DUPLICATE) { + /* + * Clear the IFF_DUPLICATE flag. + */ + if (ioctl(iph->iph_sock, SIOCGLIFADDR, &lifr) < 0) + return (ipadm_errno2status(errno)); + if (ioctl(iph->iph_sock, SIOCSLIFADDR, &lifr) < 0) + return (ipadm_errno2status(errno)); + } + + /* Update persistent DB */ + if (ipadm_flags & IPADM_OPT_PERSIST) { + status = i_ipadm_persist_propval(iph, &up_addrprop, + "no", &ipaddr, 0); + } + + return (status); +} + +/* + * Refreshes the address in the address object `aobjname'. If the address object + * is of type IPADM_ADDR_STATIC, DAD is re-initiated on the address. If + * `ipadm_flags' has IPADM_OPT_INFORM set, a DHCP_INFORM message is sent to the + * dhcpagent for this static address. If the address object is of type + * IPADM_ADDR_DHCP, a DHCP_EXTEND message is sent to the dhcpagent. + * If a dhcp address has not yet been acquired, a DHCP_START is sent to the + * dhcpagent. This operation is not supported for an address object of + * type IPADM_ADDR_IPV6_ADDRCONF. + */ +ipadm_status_t +ipadm_refresh_addr(ipadm_handle_t iph, const char *aobjname, + uint32_t ipadm_flags) +{ + ipadm_status_t status = IPADM_SUCCESS; + uint64_t flags; + struct ipadm_addrobj_s ipaddr; + sa_family_t af; + char lifname[LIFNAMSIZ]; + boolean_t inform = + ((ipadm_flags & IPADM_OPT_INFORM) != 0); + int dherr; + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + + /* validate input */ + if (aobjname == NULL || strlcpy(ipaddr.ipadm_aobjname, aobjname, + IPADM_AOBJSIZ) >= IPADM_AOBJSIZ) { + return (IPADM_INVALID_ARG); + } + + /* Retrieve the address object information. */ + status = i_ipadm_get_addrobj(iph, &ipaddr); + if (status != IPADM_SUCCESS) + return (status); + + if (!(ipaddr.ipadm_flags & IPMGMT_ACTIVE)) + return (IPADM_OP_DISABLE_OBJ); + + if (i_ipadm_is_vni(ipaddr.ipadm_ifname)) + return (IPADM_NOTSUP); + if (inform && ipaddr.ipadm_atype != IPADM_ADDR_STATIC) + return (IPADM_INVALID_ARG); + af = ipaddr.ipadm_af; + if (ipaddr.ipadm_atype == IPADM_ADDR_STATIC) { + i_ipadm_addrobj2lifname(&ipaddr, lifname, sizeof (lifname)); + status = i_ipadm_get_flags(iph, lifname, af, &flags); + if (status != IPADM_SUCCESS) + return (status); + if (inform) { + ipaddr.ipadm_wait = IPADM_DHCP_WAIT_DEFAULT; + return (i_ipadm_op_dhcp(&ipaddr, DHCP_INFORM, NULL)); + } + if (!(flags & IFF_DUPLICATE)) + return (IPADM_SUCCESS); + status = i_ipadm_set_flags(iph, lifname, af, IFF_UP, 0); + } else if (ipaddr.ipadm_atype == IPADM_ADDR_DHCP) { + status = i_ipadm_op_dhcp(&ipaddr, DHCP_EXTEND, &dherr); + /* + * Restart the dhcp address negotiation with server if no + * address has been acquired yet. + */ + if (status != IPADM_SUCCESS && dherr == DHCP_IPC_E_OUTSTATE) { + ipaddr.ipadm_wait = IPADM_DHCP_WAIT_DEFAULT; + status = i_ipadm_op_dhcp(&ipaddr, DHCP_START, NULL); + } + } else { + status = IPADM_NOTSUP; + } + return (status); +} + +/* + * This is called from ipadm_create_addr() to validate the address parameters. + * It does the following steps: + * 1. Validates the interface name. + * 2. Verifies that the interface is not an IPMP meta-interface or an + * underlying interface. + * 3. In case of a persistent operation, verifies that the interface + * is persistent. Returns error if interface is not enabled but + * is in persistent config. + * 4. Verifies that the destination address is not set or the address type is + * not DHCP or ADDRCONF when the interface is a loopback interface. + * 5. Verifies that the address type is not DHCP or ADDRCONF when the interface + * has IFF_VRRP interface flag set. + */ +static ipadm_status_t +i_ipadm_validate_create_addr(ipadm_handle_t iph, ipadm_addrobj_t ipaddr, + uint32_t flags) +{ + sa_family_t af; + sa_family_t other_af; + char *ifname; + ipadm_status_t status; + boolean_t legacy = (iph->iph_flags & IPH_LEGACY); + boolean_t islo, isvni; + uint64_t ifflags = 0; + boolean_t p_exists; + boolean_t af_exists, other_af_exists, a_exists; + + if (ipaddr == NULL || flags == 0 || flags == IPADM_OPT_PERSIST || + (flags & ~(IPADM_COMMON_OPT_MASK|IPADM_OPT_UP))) { + return (IPADM_INVALID_ARG); + } + + if (ipaddr->ipadm_af == AF_UNSPEC) + return (IPADM_BAD_ADDR); + + if (!legacy && ipaddr->ipadm_lifnum != 0) + return (IPADM_INVALID_ARG); + + if (legacy && ipaddr->ipadm_atype != IPADM_ADDR_STATIC) + return (IPADM_NOTSUP); + + ifname = ipaddr->ipadm_ifname; + + if (i_ipadm_is_ipmp(iph, ifname) || i_ipadm_is_under_ipmp(iph, ifname)) + return (IPADM_NOTSUP); + + af = ipaddr->ipadm_af; + af_exists = ipadm_if_enabled(iph, ifname, af); + /* + * For legacy case, interfaces are not implicitly plumbed. We need to + * check if the interface exists in the active configuration. + */ + if (legacy && !af_exists) + return (IPADM_ENXIO); + + other_af = (af == AF_INET ? AF_INET6 : AF_INET); + other_af_exists = ipadm_if_enabled(iph, ifname, other_af); + /* + * Check if one of the v4 or the v6 interfaces exists in the + * active configuration. An interface is considered disabled only + * if both v4 and v6 are not active. + */ + a_exists = (af_exists || other_af_exists); + + /* Check if interface exists in the persistent configuration. */ + status = i_ipadm_if_pexists(iph, ifname, af, &p_exists); + if (status != IPADM_SUCCESS) + return (status); + if (!a_exists && p_exists) + return (IPADM_OP_DISABLE_OBJ); + if ((flags & IPADM_OPT_PERSIST) && a_exists && !p_exists) { + /* + * If address has to be created persistently, + * and the interface does not exist in the persistent + * store but in active config, fail. + */ + return (IPADM_TEMPORARY_OBJ); + } + if (af_exists) { + status = i_ipadm_get_flags(iph, ifname, af, &ifflags); + if (status != IPADM_SUCCESS) + return (status); + } + + /* Perform validation steps (4) and (5) */ + islo = i_ipadm_is_loopback(ifname); + isvni = i_ipadm_is_vni(ifname); + switch (ipaddr->ipadm_atype) { + case IPADM_ADDR_STATIC: + if ((islo || isvni) && ipaddr->ipadm_static_dname[0] != '\0') + return (IPADM_INVALID_ARG); + /* Check for a valid src address */ + if (!legacy && sockaddrunspec(&ipaddr->ipadm_static_addr)) + return (IPADM_BAD_ADDR); + break; + case IPADM_ADDR_DHCP: + if (islo || (ifflags & IFF_VRRP)) + return (IPADM_NOTSUP); + break; + case IPADM_ADDR_IPV6_ADDRCONF: + if (islo || (ifflags & IFF_VRRP) || + i_ipadm_is_6to4(iph, ifname)) { + return (IPADM_NOTSUP); + } + break; + default: + return (IPADM_INVALID_ARG); + } + + return (IPADM_SUCCESS); +} + +ipadm_status_t +i_ipadm_merge_prefixlen_from_nvl(nvlist_t *invl, nvlist_t *onvl, + const char *aobjname) +{ + nvpair_t *nvp, *prefixnvp; + nvlist_t *tnvl; + char *aname; + int err; + + for (nvp = nvlist_next_nvpair(invl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(invl, nvp)) { + if (nvpair_value_nvlist(nvp, &tnvl) == 0 && + nvlist_exists(tnvl, IPADM_NVP_PREFIXLEN) && + nvlist_lookup_string(tnvl, IPADM_NVP_AOBJNAME, + &aname) == 0 && strcmp(aname, aobjname) == 0) { + /* prefixlen exists for given address object */ + (void) nvlist_lookup_nvpair(tnvl, IPADM_NVP_PREFIXLEN, + &prefixnvp); + err = nvlist_add_nvpair(onvl, prefixnvp); + if (err == 0) { + err = nvlist_remove(invl, nvpair_name(nvp), + nvpair_type(nvp)); + } + return (ipadm_errno2status(err)); + } + } + return (IPADM_SUCCESS); +} + +/* + * Re-enables the address object `aobjname' based on the saved + * configuration for `aobjname'. + */ +ipadm_status_t +ipadm_enable_addr(ipadm_handle_t iph, const char *aobjname, uint32_t flags) +{ + nvlist_t *addrnvl, *nvl; + nvpair_t *nvp; + ipadm_status_t status; + struct ipadm_addrobj_s ipaddr; + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + + /* validate input */ + if (flags & IPADM_OPT_PERSIST) + return (IPADM_NOTSUP); + if (aobjname == NULL || strlcpy(ipaddr.ipadm_aobjname, aobjname, + IPADM_AOBJSIZ) >= IPADM_AOBJSIZ) { + return (IPADM_INVALID_ARG); + } + + /* Retrieve the address object information. */ + status = i_ipadm_get_addrobj(iph, &ipaddr); + if (status != IPADM_SUCCESS) + return (status); + if (ipaddr.ipadm_flags & IPMGMT_ACTIVE) + return (IPADM_ADDROBJ_EXISTS); + + status = i_ipadm_get_db_addr(iph, NULL, aobjname, &addrnvl); + if (status != IPADM_SUCCESS) + return (status); + + assert(addrnvl != NULL); + + for (nvp = nvlist_next_nvpair(addrnvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(addrnvl, nvp)) { + if (nvpair_value_nvlist(nvp, &nvl) != 0) + continue; + + if (nvlist_exists(nvl, IPADM_NVP_IPV4ADDR) || + nvlist_exists(nvl, IPADM_NVP_IPV6ADDR)) { + status = i_ipadm_merge_prefixlen_from_nvl(addrnvl, nvl, + aobjname); + if (status != IPADM_SUCCESS) + continue; + } + iph->iph_flags |= IPH_INIT; + status = i_ipadm_init_addrobj(iph, nvl); + iph->iph_flags &= ~IPH_INIT; + if (status != IPADM_SUCCESS) + break; + } + + return (status); +} + +/* + * Disables the address object in `aobjname' from the active configuration. + * Error code return values follow the model in ipadm_delete_addr(). + */ +ipadm_status_t +ipadm_disable_addr(ipadm_handle_t iph, const char *aobjname, uint32_t flags) +{ + /* validate input */ + if (flags & IPADM_OPT_PERSIST) + return (IPADM_NOTSUP); + + return (ipadm_delete_addr(iph, aobjname, IPADM_OPT_ACTIVE)); +} diff --git a/usr/src/lib/libipadm/common/ipadm_if.c b/usr/src/lib/libipadm/common/ipadm_if.c new file mode 100644 index 0000000000..d4a24d49a4 --- /dev/null +++ b/usr/src/lib/libipadm/common/ipadm_if.c @@ -0,0 +1,1534 @@ +/* + * 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 <errno.h> +#include <sys/sockio.h> +#include <string.h> +#include <assert.h> +#include <unistd.h> +#include <stropts.h> +#include <strings.h> +#include <libdlpi.h> +#include <libdllink.h> +#include <libinetutil.h> +#include <inet/ip.h> +#include <limits.h> +#include <zone.h> +#include <ipadm_ndpd.h> +#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); + +/* + * 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; + + *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) { + ifp = calloc(1, sizeof (ipadm_if_info_t)); + if (ifp == NULL) { + status = ipadm_errno2status(errno); + goto fail; + } + (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; + 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; + if (lifrl.lifr_flags & IFF_IPMP) + ifp->ifi_cflags |= IFIF_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; + } + free(buf); + return (IPADM_SUCCESS); +fail: + free(buf); + 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; + ipmgmt_getif_arg_t getif; + ipmgmt_getif_rval_t *rvalp; + ipadm_if_info_t *ifp, *curr, *prev = NULL; + int i = 0, err = 0; + + bzero(&getif, sizeof (getif)); + if (ifname != NULL) + (void) strlcpy(getif.ia_ifname, ifname, LIFNAMSIZ); + getif.ia_cmd = IPMGMT_CMD_GETIF; + + *if_info = NULL; + + if ((rvalp = malloc(sizeof (ipmgmt_getif_rval_t))) == NULL) + return (ipadm_errno2status(errno)); + err = ipadm_door_call(iph, &getif, sizeof (getif), (void **)&rvalp, + sizeof (*rvalp), B_TRUE); + if (err == ENOENT) { + free(rvalp); + if (ifname != NULL) + return (ipadm_errno2status(err)); + return (IPADM_SUCCESS); + } else if (err != 0) { + free(rvalp); + return (ipadm_errno2status(err)); + } + + ifp = rvalp->ir_ifinfo; + for (i = 0; i < rvalp->ir_ifcnt; i++) { + ifp = rvalp->ir_ifinfo + i; + if ((curr = malloc(sizeof (*curr))) == NULL) { + status = ipadm_errno2status(errno); + ipadm_free_if_info(prev); + break; + } + (void) bcopy(ifp, curr, sizeof (*curr)); + curr->ifi_next = prev; + prev = curr; + } + *if_info = curr; + free(rvalp); + return (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; + /* + * 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) { + for (aifp = aifinfo; aifp != NULL; aifp = aifp->ifi_next) { + if (strcmp(aifp->ifi_name, pifp->ifi_name) == 0) { + aifp->ifi_pflags = pifp->ifi_pflags; + break; + } + } + if (aifp == NULL) { + aifp = malloc(sizeof (ipadm_if_info_t)); + if (aifp == NULL) { + status = ipadm_errno2status(errno); + goto fail; + } + *aifp = *pifp; + aifp->ifi_next = NULL; + aifp->ifi_state = IFIS_DISABLED; + if (last != NULL) + last->ifi_next = aifp; + else + aifinfo = aifp; + last = aifp; + } + } + *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); +} + +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; + + 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))); + free(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 (getzoneid() == 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); + 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); + 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) +{ + ipmgmt_if_arg_t ifarg; + int err; + + (void) strlcpy(ifarg.ia_ifname, ifname, sizeof (ifarg.ia_ifname)); + ifarg.ia_family = af; + 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); + + 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); +} + +/* + * 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; + free(ifinfo); + } +} + +/* + * 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) +{ + 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 persistent + * db. The interface is already available in persistent db + * and we are here to re-enable the persistent configuration. + */ + iph->iph_flags |= IPH_INIT; + status = i_ipadm_init_ifobj(iph, ifname, ifnvl); + iph->iph_flags &= ~IPH_INIT; + 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); + } +} diff --git a/usr/src/lib/libipadm/common/ipadm_ipmgmt.h b/usr/src/lib/libipadm/common/ipadm_ipmgmt.h new file mode 100644 index 0000000000..7bd0d19f23 --- /dev/null +++ b/usr/src/lib/libipadm/common/ipadm_ipmgmt.h @@ -0,0 +1,272 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _IPADM_IPMGMT_H +#define _IPADM_IPMGMT_H + +#ifdef __cplusplus +extern "C" { +#endif +#include <sys/types.h> +#include <sys/stat.h> +#include <fcntl.h> +#include <sys/mman.h> +#include <door.h> +#include <libipadm.h> +#include <inet/tunables.h> + +/* + * Function declarations and data structures shared by libipadm.so and + * the IP management daemon. + */ + +/* Authorization required to configure network interfaces */ +#define NETWORK_INTERFACE_CONFIG_AUTH "solaris.network.interface.config" + +/* + * Data store read/write utilities related declarations. + */ +/* Permanent data store for ipadm */ +#define IPADM_DB_FILE "/etc/ipadm/ipadm.conf" +#define IPADM_FILE_MODE (S_IRUSR | S_IWUSR | S_IRGRP | S_IROTH) +#define IPADM_TMPFS_DIR "/etc/svc/volatile/ipadm" + +/* + * For more information on these definitions please refer to the top of + * ipadm_persist.c. These are the name of the nvpairs which hold the + * respective values. All nvpairs private to ipadm have names that begin + * with "_". Note below that 'prefixlen' is an address property and therefore + * not a private nvpair name. + */ +#define IPADM_NVP_PROTONAME "_protocol" /* protocol name */ +#define IPADM_NVP_IFNAME "_ifname" /* interface name */ +#define IPADM_NVP_AOBJNAME "_aobjname" /* addrobj name */ +#define IPADM_NVP_FAMILY "_family" /* address family */ +#define IPADM_NVP_IPV4ADDR "_ipv4addr" /* name of IPv4 addr nvlist */ +#define IPADM_NVP_IPNUMADDR "_addr" /* local address */ +#define IPADM_NVP_IPADDRHNAME "_aname" /* local hostname */ +#define IPADM_NVP_IPDADDRHNAME "_dname" /* remote hostname */ +#define IPADM_NVP_PREFIXLEN "prefixlen" /* prefixlen */ +#define IPADM_NVP_IPV6ADDR "_ipv6addr" /* name of IPv6 addr nvlist */ +#define IPADM_NVP_DHCP "_dhcp" /* name of DHCP nvlist */ +#define IPADM_NVP_WAIT "_wait" /* DHCP timeout value */ +#define IPADM_NVP_PRIMARY "_primary" /* DHCP primary interface */ +#define IPADM_NVP_LIFNUM "_lifnum" /* logical interface number */ +#define IPADM_NVP_INTFID "_intfid" /* name of IPv6 intfid nvlist */ +#define IPADM_NVP_STATELESS "_stateless" /* IPv6 autoconf stateless */ +#define IPADM_NVP_STATEFUL "_stateful" /* IPv6 autoconf dhcpv6 */ + +#define IPADM_PRIV_NVP(s) ((s)[0] == '_') + +/* data-store operations */ +typedef enum { + IPADM_DB_WRITE = 0, /* Writes to DB */ + IPADM_DB_DELETE, /* Deletes an entry from DB */ + IPADM_DB_READ /* Read from DB */ +} ipadm_db_op_t; + +/* + * callback arg used by db_wfunc_t that writes to DB. The contents to be + * written to DB are captured in `dbw_nvl'. + */ +typedef struct ipadm_dbwrite_cbarg_s { + nvlist_t *dbw_nvl; + uint_t dbw_flags; +} ipadm_dbwrite_cbarg_t; + +/* + * door related function declarations and data structures. + */ + +/* The door file for the ipmgmt (ip-interface management) daemon */ +#define IPMGMT_DOOR "/etc/svc/volatile/ipadm/ipmgmt_door" +#define MAXPROTONAMELEN 32 + +/* door call command type */ +typedef enum { + IPMGMT_CMD_SETPROP = 1, /* persist property */ + IPMGMT_CMD_SETIF, /* persist interface */ + IPMGMT_CMD_SETADDR, /* persist address */ + IPMGMT_CMD_GETPROP, /* retrieve persisted property value */ + IPMGMT_CMD_GETIF, /* retrieve persisted interface conf. */ + IPMGMT_CMD_GETADDR, /* retrieve persisted addresses */ + IPMGMT_CMD_RESETIF, /* purge interface configuration */ + IPMGMT_CMD_RESETADDR, /* purge address configuration */ + IPMGMT_CMD_RESETPROP, /* purge property configuration */ + IPMGMT_CMD_INITIF, /* retrieve interfaces to initialize */ + IPMGMT_CMD_ADDROBJ_LOOKUPADD, /* addr. object lookup & add */ + IPMGMT_CMD_ADDROBJ_ADD, /* add addr. object to addrobj map */ + IPMGMT_CMD_LIF2ADDROBJ, /* lifname to addrobj mapping */ + IPMGMT_CMD_AOBJNAME2ADDROBJ /* aobjname to addrobj mapping */ +} ipmgmt_door_cmd_type_t; + +/* + * Note: We need to keep the size of the structure the same on amd64 and i386 + * for all door_call arguments and door_return structures. + */ +/* door_call argument */ +typedef struct ipmgmt_arg { + ipmgmt_door_cmd_type_t ia_cmd; +} ipmgmt_arg_t; + +/* IPMGMT_CMD_{SETPROP|GETPROP|RESETPROP} door_call argument */ +typedef struct ipmgmt_prop_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + char ia_ifname[LIFNAMSIZ]; + char ia_aobjname[IPADM_AOBJSIZ]; + char ia_module[MAXPROTONAMELEN]; + char ia_pname[MAXPROPNAMELEN]; + char ia_pval[MAXPROPVALLEN]; +} ipmgmt_prop_arg_t; +/* + * ia_flags used in ipmgmt_prop_arg_t. + * - APPEND updates the multi-valued property entry with a new value + * - REDUCE updates the multi-valued property entry by removing a value + */ +#define IPMGMT_APPEND 0x00000001 +#define IPMGMT_REMOVE 0x00000002 + +/* IPMGMT_CMD_GETIF door_call argument structure */ +typedef struct ipmgmt_getif_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + char ia_ifname[LIFNAMSIZ]; +} ipmgmt_getif_arg_t; + +/* IPMGMT_CMD_RESETIF, IPMGMT_CMD_SETIF door_call argument structure */ +typedef struct ipmgmt_if_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + char ia_ifname[LIFNAMSIZ]; + sa_family_t ia_family; +} ipmgmt_if_arg_t; + +/* IPMGMT_CMD_INITIF door_call argument structure */ +typedef struct ipmgmt_initif_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + sa_family_t ia_family; + size_t ia_nvlsize; + /* packed nvl follows */ +} ipmgmt_initif_arg_t; + +/* IPMGMT_CMD_SETADDR door_call argument */ +typedef struct ipmgmt_setaddr_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + size_t ia_nvlsize; + /* packed nvl follows */ +} ipmgmt_setaddr_arg_t; + +/* IPMGMT_CMD_GETADDR door_call argument */ +typedef struct ipmgmt_getaddr_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + char ia_ifname[LIFNAMSIZ]; + sa_family_t ia_family; + char ia_aobjname[IPADM_AOBJSIZ]; +} ipmgmt_getaddr_arg_t; + +/* IPMGMT_CMD_RESETADDR door_call argument */ +typedef struct ipmgmt_addr_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + char ia_aobjname[IPADM_AOBJSIZ]; + int32_t ia_lnum; +} ipmgmt_addr_arg_t; + +/* + * IPMGMT_CMD_{ADDROBJ_ADD|ADDROBJ_LOOKUPADD|LIFNUM2ADDROBJ| + * ADDROBJ2LIFNUM} door_call argument. + */ +typedef struct ipmgmt_aobjop_arg_s { + ipmgmt_door_cmd_type_t ia_cmd; + uint32_t ia_flags; + char ia_aobjname[IPADM_AOBJSIZ]; + char ia_ifname[LIFNAMSIZ]; + int32_t ia_lnum; + sa_family_t ia_family; + ipadm_addr_type_t ia_atype; +} ipmgmt_aobjop_arg_t; + +/* + * ia_flags used inside the arguments for interface/address commands + * - ACTIVE updates the running configuration + * - PERSIST updates the permanent data store + * - INIT indicates that operation being performed is under init + * context + */ +#define IPMGMT_ACTIVE 0x00000001 +#define IPMGMT_PERSIST 0x00000002 +#define IPMGMT_INIT 0x00000004 + +/* door call return value */ +typedef struct ipmgmt_retval_s { + int32_t ir_err; +} ipmgmt_retval_t; + +/* IPMGMT_CMD_GETADDR door_return value */ +typedef struct ipmgmt_get_rval_s { + int32_t ir_err; + size_t ir_nvlsize; + /* packed nvl follows */ +} ipmgmt_get_rval_t; + +/* IPMGMT_CMD_GETPROP door_return value */ +typedef struct ipmgmt_getprop_rval_s { + int32_t ir_err; + char ir_pval[MAXPROPVALLEN]; +} ipmgmt_getprop_rval_t; + +/* IPMGMT_CMD_GETIF door_return value */ +typedef struct ipmgmt_getif_rval_s { + int32_t ir_err; + uint32_t ir_ifcnt; + ipadm_if_info_t ir_ifinfo[1]; +} ipmgmt_getif_rval_t; + +/* IPMGMT_CMD_{LOOKUPADD|LIFNUM2ADDROBJ|ADDROBJ2LIFNUM} door_return value */ +typedef struct ipmgmt_aobjop_rval_s { + int32_t ir_err; + char ir_aobjname[IPADM_AOBJSIZ]; + char ir_ifname[LIFNAMSIZ]; + int32_t ir_lnum; + sa_family_t ir_family; + uint32_t ir_flags; + ipadm_addr_type_t ir_atype; + struct sockaddr_storage ir_ifid; +} ipmgmt_aobjop_rval_t; + +/* DB walk callback functions */ +typedef boolean_t db_wfunc_t(void *, nvlist_t *, char *, size_t, int *); +extern int ipadm_rw_db(db_wfunc_t *, void *, const char *, mode_t, + ipadm_db_op_t); + +#ifdef __cplusplus +} +#endif + +#endif /* _IPADM_IPMGMT_H */ diff --git a/usr/src/lib/libipadm/common/ipadm_ndpd.c b/usr/src/lib/libipadm/common/ipadm_ndpd.c new file mode 100644 index 0000000000..2030295e24 --- /dev/null +++ b/usr/src/lib/libipadm/common/ipadm_ndpd.c @@ -0,0 +1,366 @@ +/* + * 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. + */ + +/* + * This file contains the functions that are required for communicating + * with in.ndpd while creating autoconfigured addresses. + */ + +#include <stdio.h> +#include <stdlib.h> +#include <string.h> +#include <strings.h> +#include <errno.h> +#include <fcntl.h> +#include <unistd.h> +#include <sys/sockio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <inet/ip.h> +#include <arpa/inet.h> +#include <assert.h> +#include <poll.h> +#include <ipadm_ndpd.h> +#include "libipadm_impl.h" + +#define NDPDTIMEOUT 5000 +#define PREFIXLEN_LINKLOCAL 10 + +static ipadm_status_t i_ipadm_create_linklocal(ipadm_handle_t, + ipadm_addrobj_t); +static void i_ipadm_make_linklocal(struct sockaddr_in6 *, + const struct in6_addr *); +static ipadm_status_t i_ipadm_send_ndpd_cmd(const char *, + const struct ipadm_addrobj_s *, int); + +/* + * Sends message to in.ndpd asking not to do autoconf for the given interface, + * until IPADM_CREATE_ADDRS or IPADM_ENABLE_AUTOCONF is sent. + */ +ipadm_status_t +i_ipadm_disable_autoconf(const char *ifname) +{ + return (i_ipadm_send_ndpd_cmd(ifname, NULL, IPADM_DISABLE_AUTOCONF)); +} + +/* + * Sends message to in.ndpd to enable autoconf for the given interface, + * until another IPADM_DISABLE_AUTOCONF is sent. + */ +ipadm_status_t +i_ipadm_enable_autoconf(const char *ifname) +{ + return (i_ipadm_send_ndpd_cmd(ifname, NULL, IPADM_ENABLE_AUTOCONF)); +} + +ipadm_status_t +i_ipadm_create_ipv6addrs(ipadm_handle_t iph, ipadm_addrobj_t addr, + uint32_t i_flags) +{ + ipadm_status_t status; + + /* + * Create the link local based on the given token. If the same intfid + * was already used with a different address object, this step will + * fail. + */ + status = i_ipadm_create_linklocal(iph, addr); + if (status != IPADM_SUCCESS) + return (status); + + /* + * Request in.ndpd to start the autoconfiguration. + * If autoconfiguration was already started by another means (e.g. + * "ifconfig" ), in.ndpd will return EEXIST. + */ + if (addr->ipadm_stateless || addr->ipadm_stateful) { + status = i_ipadm_send_ndpd_cmd(addr->ipadm_ifname, addr, + IPADM_CREATE_ADDRS); + if (status != IPADM_SUCCESS && + status != IPADM_NDPD_NOT_RUNNING) { + (void) i_ipadm_delete_addr(iph, addr); + return (status); + } + } + + /* Persist the intfid. */ + status = i_ipadm_addr_persist(iph, addr, B_FALSE, i_flags); + if (status != IPADM_SUCCESS) { + (void) i_ipadm_delete_addr(iph, addr); + (void) i_ipadm_send_ndpd_cmd(addr->ipadm_ifname, addr, + IPADM_DELETE_ADDRS); + } + + return (status); +} + +ipadm_status_t +i_ipadm_delete_ipv6addrs(ipadm_handle_t iph, ipadm_addrobj_t ipaddr) +{ + ipadm_status_t status; + + /* + * Send a msg to in.ndpd to remove the autoconfigured addresses, + * and delete the link local that was created. + */ + status = i_ipadm_send_ndpd_cmd(ipaddr->ipadm_ifname, ipaddr, + IPADM_DELETE_ADDRS); + if (status == IPADM_NDPD_NOT_RUNNING) + status = IPADM_SUCCESS; + if (status == IPADM_SUCCESS) + status = i_ipadm_delete_addr(iph, ipaddr); + + return (status); +} + +static ipadm_status_t +i_ipadm_create_linklocal(ipadm_handle_t iph, ipadm_addrobj_t addr) +{ + boolean_t addif = B_FALSE; + struct sockaddr_in6 *sin6; + struct lifreq lifr; + int err; + ipadm_status_t status; + in6_addr_t ll_template = {0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, addr->ipadm_ifname, LIFNAMSIZ); + + if ((err = ioctl(iph->iph_sock6, SIOCGLIFADDR, (caddr_t)&lifr)) < 0) + return (ipadm_errno2status(errno)); + + /* + * If no address exists on 0th logical interface, + * create link-local address on it. Else, create a new + * logical interface. + */ + sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; + if (!IN6_IS_ADDR_UNSPECIFIED((&sin6->sin6_addr))) { + if (ioctl(iph->iph_sock6, SIOCLIFADDIF, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + addr->ipadm_lifnum = i_ipadm_get_lnum(lifr.lifr_name); + addif = B_TRUE; + } + /* Create the link-local address */ + bzero(&lifr.lifr_addr, sizeof (lifr.lifr_addr)); + (void) plen2mask(PREFIXLEN_LINKLOCAL, AF_INET6, &lifr.lifr_addr); + if ((err = ioctl(iph->iph_sock6, SIOCSLIFNETMASK, (caddr_t)&lifr)) < 0) + goto fail; + if (addr->ipadm_intfidlen == 0) { + /* + * If we have to use the default interface id, + * we just need to set the prefix to the link-local prefix. + * SIOCSLIFPREFIX sets the address with the given prefix + * and the default interface id. + */ + sin6->sin6_addr = ll_template; + err = ioctl(iph->iph_sock6, SIOCSLIFPREFIX, (caddr_t)&lifr); + if (err < 0) + goto fail; + } else { + /* Make a linklocal address in sin6 and set it */ + i_ipadm_make_linklocal(sin6, &addr->ipadm_intfid.sin6_addr); + err = ioctl(iph->iph_sock6, SIOCSLIFADDR, (caddr_t)&lifr); + if (err < 0) + goto fail; + } + if ((err = ioctl(iph->iph_sock6, SIOCGLIFFLAGS, (char *)&lifr)) < 0) + goto fail; + lifr.lifr_flags |= IFF_UP; + if ((err = ioctl(iph->iph_sock6, SIOCSLIFFLAGS, (char *)&lifr)) < 0) + goto fail; + return (IPADM_SUCCESS); + +fail: + if (errno == EEXIST) + status = IPADM_ADDRCONF_EXISTS; + else + status = ipadm_errno2status(errno); + /* Remove the linklocal that was created. */ + if (addif) { + (void) ioctl(iph->iph_sock6, SIOCLIFREMOVEIF, (caddr_t)&lifr); + } else { + struct sockaddr_in6 *sin6; + + sin6 = (struct sockaddr_in6 *)&lifr.lifr_addr; + lifr.lifr_flags &= ~IFF_UP; + (void) ioctl(iph->iph_sock6, SIOCSLIFFLAGS, (caddr_t)&lifr); + sin6->sin6_family = AF_INET6; + sin6->sin6_addr = in6addr_any; + (void) ioctl(iph->iph_sock6, SIOCSLIFADDR, (caddr_t)&lifr); + } + return (status); +} + +/* + * Make a linklocal address based on the given intfid and copy it into + * the output parameter `sin6'. + */ +static void +i_ipadm_make_linklocal(struct sockaddr_in6 *sin6, const struct in6_addr *intfid) +{ + int i; + in6_addr_t ll_template = {0xfe, 0x80, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, + 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0, 0x0 }; + + sin6->sin6_family = AF_INET6; + sin6->sin6_addr = *intfid; + for (i = 0; i < 4; i++) { + sin6->sin6_addr.s6_addr[i] = + sin6->sin6_addr.s6_addr[i] | ll_template.s6_addr[i]; + } +} + +/* + * Function that forms an ndpd msg and sends it to the in.ndpd daemon's loopback + * listener socket. + */ +static ipadm_status_t +i_ipadm_send_ndpd_cmd(const char *ifname, const struct ipadm_addrobj_s *addr, + int cmd) +{ + int fd; + struct sockaddr_un servaddr; + int flags; + ipadm_ndpd_msg_t msg; + int retval; + + if (addr == NULL && + (cmd == IPADM_CREATE_ADDRS || cmd == IPADM_DELETE_ADDRS)) { + return (IPADM_INVALID_ARG); + } + + fd = socket(AF_UNIX, SOCK_STREAM, 0); + if (fd == -1) + return (IPADM_FAILURE); + + /* Put the socket in non-blocking mode */ + flags = fcntl(fd, F_GETFL, 0); + if (flags != -1) + (void) fcntl(fd, F_SETFL, flags | O_NONBLOCK); + + /* Connect to in.ndpd */ + bzero(&servaddr, sizeof (servaddr)); + servaddr.sun_family = AF_UNIX; + (void) strlcpy(servaddr.sun_path, IPADM_UDS_PATH, + sizeof (servaddr.sun_path)); + if (connect(fd, (struct sockaddr *)&servaddr, sizeof (servaddr)) == -1) + goto fail; + + bzero(&msg, sizeof (msg)); + msg.inm_cmd = cmd; + (void) strlcpy(msg.inm_ifname, ifname, sizeof (msg.inm_ifname)); + if (addr != NULL) { + msg.inm_intfid = addr->ipadm_intfid; + msg.inm_intfidlen = addr->ipadm_intfidlen; + msg.inm_stateless = addr->ipadm_stateless; + msg.inm_stateful = addr->ipadm_stateful; + if (cmd == IPADM_CREATE_ADDRS) { + (void) strlcpy(msg.inm_aobjname, addr->ipadm_aobjname, + sizeof (msg.inm_aobjname)); + } + } + if (ipadm_ndpd_write(fd, &msg, sizeof (msg)) < 0) + goto fail; + if (ipadm_ndpd_read(fd, &retval, sizeof (retval)) < 0) + goto fail; + (void) close(fd); + if (cmd == IPADM_CREATE_ADDRS && retval == EEXIST) + return (IPADM_ADDRCONF_EXISTS); + return (ipadm_errno2status(retval)); +fail: + (void) close(fd); + return (IPADM_NDPD_NOT_RUNNING); +} + +/* + * Attempt to read `buflen' worth of bytes from `fd' into the buffer pointed + * to by `buf'. + */ +int +ipadm_ndpd_read(int fd, void *buffer, size_t buflen) +{ + int retval; + ssize_t nbytes = 0; /* total bytes processed */ + ssize_t prbytes; /* per-round bytes processed */ + struct pollfd pfd; + + while (nbytes < buflen) { + + pfd.fd = fd; + pfd.events = POLLIN; + + /* + * Wait for data to come in or for the timeout to fire. + */ + retval = poll(&pfd, 1, NDPDTIMEOUT); + if (retval <= 0) { + if (retval == 0) + errno = ETIME; + break; + } + + /* + * Descriptor is ready; have at it. + */ + prbytes = read(fd, (caddr_t)buffer + nbytes, buflen - nbytes); + if (prbytes <= 0) { + if (prbytes == -1 && errno == EINTR) + continue; + break; + } + nbytes += prbytes; + } + + return (nbytes == buflen ? 0 : -1); +} + +/* + * Write `buflen' bytes from `buffer' to open file `fd'. Returns 0 + * if all requested bytes were written, or an error code if not. + */ +int +ipadm_ndpd_write(int fd, const void *buffer, size_t buflen) +{ + size_t nwritten; + ssize_t nbytes; + const char *buf = buffer; + + for (nwritten = 0; nwritten < buflen; nwritten += nbytes) { + nbytes = write(fd, &buf[nwritten], buflen - nwritten); + if (nbytes == -1) + return (-1); + if (nbytes == 0) { + errno = EIO; + return (-1); + } + } + + assert(nwritten == buflen); + return (0); +} diff --git a/usr/src/lib/libipadm/common/ipadm_ndpd.h b/usr/src/lib/libipadm/common/ipadm_ndpd.h new file mode 100644 index 0000000000..f35f8d9b7a --- /dev/null +++ b/usr/src/lib/libipadm/common/ipadm_ndpd.h @@ -0,0 +1,72 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _IPADM_NDPD_H +#define _IPADM_NDPD_H + +#ifdef __cplusplus +extern "C" { +#endif +#include <libipadm.h> + +/* File used for the AF_UNIX socket used in communicating with in.ndpd */ +#define IPADM_UDS_PATH "/var/run/in.ndpd_ipadm" + +/* Types of messages sent to in.ndpd */ +enum { + IPADM_DISABLE_AUTOCONF, + IPADM_ENABLE_AUTOCONF, + IPADM_CREATE_ADDRS, + IPADM_DELETE_ADDRS +}; + +/* Message format sent to in.ndpd */ +typedef struct ipadm_ndpd_msg_s { + uint32_t inm_cmd; + char inm_ifname[LIFNAMSIZ]; + struct sockaddr_in6 inm_intfid; + int inm_intfidlen; + boolean_t inm_stateless; + boolean_t inm_stateful; + char inm_aobjname[MAXNAMELEN]; +} ipadm_ndpd_msg_t; + +/* Functions to send to and receive from in.ndpd */ +extern int ipadm_ndpd_write(int, const void *, size_t); +extern int ipadm_ndpd_read(int, void *, size_t); + +/* + * Functions used by in.ndpd to add and delete address objects while + * adding/deleting each stateless/stateful autoconfigured address. + */ +extern ipadm_status_t ipadm_add_aobjname(ipadm_handle_t, const char *, + sa_family_t, const char *, ipadm_addr_type_t, int); +extern ipadm_status_t ipadm_delete_aobjname(ipadm_handle_t, const char *, + sa_family_t, const char *, ipadm_addr_type_t, int); + +#ifdef __cplusplus +} +#endif + +#endif /* _IPADM_NDPD_H */ diff --git a/usr/src/lib/libipadm/common/ipadm_persist.c b/usr/src/lib/libipadm/common/ipadm_persist.c new file mode 100644 index 0000000000..698d4af359 --- /dev/null +++ b/usr/src/lib/libipadm/common/ipadm_persist.c @@ -0,0 +1,828 @@ +/* + * 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. + */ + +/* + * This file contains routines to read/write formatted entries from/to + * libipadm data store /etc/ipadm/ipadm.conf. Each entry in the DB is a + * series of IPADM_NVPAIR_SEP separated (name, value) pairs, as shown + * below: + * name=value[;...] + * + * The 'name' determines how to interpret 'value'. The supported names are: + * + * IPADM_NVP_IPV6ADDR - value holds local and remote IPv6 addresses and when + * converted to nvlist, will contain nvpairs for local and remote + * addresses. These nvpairs are of type DATA_TYPE_STRING + * + * IPADM_NVP_IPV4ADDR - value holds local and remote IPv4 addresses and when + * converted to nvlist, will contain nvpairs for local and remote + * addresses. These nvpairs are of type DATA_TYPE_STRING + * + * IPADM_NVP_INTFID - value holds token, prefixlen, stateless and stateful + * info and when converted to nvlist, will contain following nvpairs + * interface_id: DATA_TYPE_UINT8_ARRAY + * prefixlen: DATA_TYPE_UINT32 + * stateless: DATA_TYPE_STRING + * stateful: DATA_TYPE_STRING + * + * IPADM_NVP_DHCP - value holds wait time and primary info and when converted + * to nvlist, will contain following nvpairs + * wait: DATA_TYPE_INT32 + * primary: DATA_TYPE_BOOLEAN + * + * default - value is a single entity and when converted to nvlist, will + * contain nvpair of type DATA_TYPE_STRING. nvpairs private to + * ipadm are of this type. Further the property name and property + * values are stored as nvpairs of this type. + * + * The syntax for each line is described above the respective functions below. + */ + +#include <stdlib.h> +#include <strings.h> +#include <errno.h> +#include <ctype.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/dld.h> +#include <fcntl.h> +#include <dirent.h> +#include <unistd.h> +#include <assert.h> +#include <sys/socket.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/sockio.h> +#include "libipadm_impl.h" + +#define MAXLINELEN 1024 +#define IPADM_NVPAIR_SEP ";" +#define IPADM_NAME_SEP "," + +static char ipadm_rootdir[MAXPATHLEN] = "/"; + +static int ipadm_process_db_line(db_wfunc_t *, void *, FILE *fp, FILE *nfp, + ipadm_db_op_t); + +/* + * convert nvpair to a "name=value" string for writing to the DB. + */ +typedef size_t ipadm_wfunc_t(nvpair_t *, char *, size_t); + +/* + * ipadm_rfunc_t takes (`name', `value') and adds the appropriately typed + * nvpair to the nvlist. + */ +typedef void ipadm_rfunc_t(nvlist_t *, char *name, char *value); + +static ipadm_rfunc_t i_ipadm_str_dbline2nvl, i_ipadm_ip4_dbline2nvl, + i_ipadm_ip6_dbline2nvl, i_ipadm_intfid_dbline2nvl, + i_ipadm_dhcp_dbline2nvl; + +static ipadm_wfunc_t i_ipadm_str_nvp2dbline, i_ipadm_ip4_nvp2dbline, + i_ipadm_ip6_nvp2dbline, i_ipadm_intfid_nvp2dbline, + i_ipadm_dhcp_nvp2dbline; + +/* + * table of function pointers to read/write formatted entries from/to + * ipadm.conf. + */ +typedef struct ipadm_conf_ent_s { + const char *ipent_type_name; + ipadm_wfunc_t *ipent_wfunc; + ipadm_rfunc_t *ipent_rfunc; +} ipadm_conf_ent_t; + +static ipadm_conf_ent_t ipadm_conf_ent[] = { + { IPADM_NVP_IPV6ADDR, i_ipadm_ip6_nvp2dbline, i_ipadm_ip6_dbline2nvl }, + { IPADM_NVP_IPV4ADDR, i_ipadm_ip4_nvp2dbline, i_ipadm_ip4_dbline2nvl }, + { IPADM_NVP_INTFID, i_ipadm_intfid_nvp2dbline, + i_ipadm_intfid_dbline2nvl }, + { IPADM_NVP_DHCP, i_ipadm_dhcp_nvp2dbline, i_ipadm_dhcp_dbline2nvl }, + { NULL, i_ipadm_str_nvp2dbline, i_ipadm_str_dbline2nvl } +}; + +static ipadm_conf_ent_t * +i_ipadm_find_conf_type(const char *type) +{ + int i; + + for (i = 0; ipadm_conf_ent[i].ipent_type_name != NULL; i++) + if (strcmp(type, ipadm_conf_ent[i].ipent_type_name) == 0) + break; + return (&ipadm_conf_ent[i]); +} + +/* + * Extracts the hostnames IPADM_NVP_IPADDRHNAME and IPADM_NVP_IPDADDRHNAME from + * the given nvlist `nvl' and adds the strings to `buf'. + */ +size_t +i_ipadm_ip_addhostname2dbline(nvlist_t *nvl, char *buf, size_t buflen) +{ + char *cp; + char tmpbuf[IPADM_STRSIZE]; + + /* Add the local hostname */ + if (nvlist_lookup_string(nvl, IPADM_NVP_IPADDRHNAME, &cp) != 0) + return (0); + (void) strlcat(buf, cp, buflen); /* local hostname */ + + /* Add the dst hostname */ + if (nvlist_lookup_string(nvl, IPADM_NVP_IPDADDRHNAME, &cp) != 0) { + /* no dst addr. just add a NULL character */ + (void) snprintf(tmpbuf, sizeof (tmpbuf), ","); + } else { + (void) snprintf(tmpbuf, sizeof (tmpbuf), ",%s", cp); + } + return (strlcat(buf, tmpbuf, buflen)); +} + +/* + * Converts IPADM_NVP_IPV4ADDR nvpair to a string representation for writing to + * the DB. The converted string format: + * ipv4addr=<local numeric IP string or hostname,remote numeric IP + * string or hostname> + */ +static size_t +i_ipadm_ip4_nvp2dbline(nvpair_t *nvp, char *buf, size_t buflen) +{ + nvlist_t *v; + int nbytes; + + assert(nvpair_type(nvp) == DATA_TYPE_NVLIST && + strcmp(nvpair_name(nvp), IPADM_NVP_IPV4ADDR) == 0); + + (void) snprintf(buf, buflen, "%s=", IPADM_NVP_IPV4ADDR); + if (nvpair_value_nvlist(nvp, &v) != 0) + goto fail; + nbytes = i_ipadm_ip_addhostname2dbline(v, buf, buflen); + if (nbytes != 0) + return (nbytes); +fail: + buf[0] = '\0'; + return (0); +} + +/* + * Converts IPADM_NVP_IPV6ADDR nvpair to a string representation for writing to + * the DB. The converted string format: + * ipv6addr=<local numeric IP string or hostname,remote numeric IP + * string or hostname> + */ +static size_t +i_ipadm_ip6_nvp2dbline(nvpair_t *nvp, char *buf, size_t buflen) +{ + nvlist_t *v; + int nbytes; + + assert(nvpair_type(nvp) == DATA_TYPE_NVLIST && + strcmp(nvpair_name(nvp), IPADM_NVP_IPV6ADDR) == 0); + + (void) snprintf(buf, buflen, "%s=", IPADM_NVP_IPV6ADDR); + if (nvpair_value_nvlist(nvp, &v) != 0) + goto fail; + nbytes = i_ipadm_ip_addhostname2dbline(v, buf, buflen); + if (nbytes != 0) + return (nbytes); +fail: + buf[0] = '\0'; + return (0); +} + +/* + * Converts IPADM_NVP_INTFID nvpair to a string representation for writing to + * the DB. The converted string format: + * IPADM_NVP_INTFID=<intfid/prefixlen>,{yes|no},{yes|no} + */ +static size_t +i_ipadm_intfid_nvp2dbline(nvpair_t *nvp, char *buf, size_t buflen) +{ + char addrbuf[IPADM_STRSIZE]; + nvlist_t *v; + uint32_t prefixlen; + struct in6_addr in6addr; + char *stateless; + char *stateful; + + assert(nvpair_type(nvp) == DATA_TYPE_NVLIST && + strcmp(nvpair_name(nvp), IPADM_NVP_INTFID) == 0); + + (void) snprintf(buf, buflen, "%s=", IPADM_NVP_INTFID); + if (nvpair_value_nvlist(nvp, &v) != 0) + goto fail; + if (i_ipadm_nvl2in6_addr(v, IPADM_NVP_IPNUMADDR, &in6addr) != + IPADM_SUCCESS) + goto fail; + (void) inet_ntop(AF_INET6, &in6addr, addrbuf, + sizeof (addrbuf)); + (void) strlcat(buf, addrbuf, buflen); + if (nvlist_lookup_uint32(v, IPADM_NVP_PREFIXLEN, &prefixlen) != 0 || + nvlist_lookup_string(v, IPADM_NVP_STATELESS, &stateless) != 0 || + nvlist_lookup_string(v, IPADM_NVP_STATEFUL, &stateful) != 0) + goto fail; + (void) snprintf(addrbuf, sizeof (addrbuf), "/%d,%s,%s", + prefixlen, stateless, stateful); + return (strlcat(buf, addrbuf, buflen)); +fail: + buf[0] = '\0'; + return (0); +} + +/* + * Converts IPADM_NVP_DHCP nvpair to a string representation for writing to the + * DB. The converted string format: + * IPADM_NVP_DHCP=<wait_time>,{yes|no} + */ +static size_t +i_ipadm_dhcp_nvp2dbline(nvpair_t *nvp, char *buf, size_t buflen) +{ + char addrbuf[IPADM_STRSIZE]; + int32_t wait; + boolean_t primary; + nvlist_t *v; + + assert(nvpair_type(nvp) == DATA_TYPE_NVLIST && + strcmp(nvpair_name(nvp), IPADM_NVP_DHCP) == 0); + + if (nvpair_value_nvlist(nvp, &v) != 0 || + nvlist_lookup_int32(v, IPADM_NVP_WAIT, &wait) != 0 || + nvlist_lookup_boolean_value(v, IPADM_NVP_PRIMARY, &primary) != 0) { + return (0); + } + (void) snprintf(buf, buflen, "%s=", IPADM_NVP_DHCP); + (void) snprintf(addrbuf, sizeof (addrbuf), "%d,%s", wait, + (primary ? "yes" : "no")); + return (strlcat(buf, addrbuf, buflen)); +} + +/* + * Constructs a "<name>=<value>" string from the nvpair, whose type must + * be STRING. + */ +static size_t +i_ipadm_str_nvp2dbline(nvpair_t *nvp, char *buf, size_t buflen) +{ + char *str = NULL; + + assert(nvpair_type(nvp) == DATA_TYPE_STRING); + if (nvpair_value_string(nvp, &str) != 0) + return (0); + return (snprintf(buf, buflen, "%s=%s", nvpair_name(nvp), str)); +} + +/* + * Converts a nvlist to string of the form: + * <prop0>=<val0>,...,<valn>;...;<propn>=<val0>,...,<valn>; + */ +size_t +ipadm_nvlist2str(nvlist_t *nvl, char *buf, size_t buflen) +{ + nvpair_t *nvp = NULL; + uint_t nbytes = 0, tbytes = 0; + ipadm_conf_ent_t *ipent; + size_t bufsize = buflen; + + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + ipent = i_ipadm_find_conf_type(nvpair_name(nvp)); + nbytes = (*ipent->ipent_wfunc)(nvp, buf, buflen); + /* add nvpair separator */ + nbytes += snprintf(buf + nbytes, buflen - nbytes, "%s", + IPADM_NVPAIR_SEP); + buflen -= nbytes; + buf += nbytes; + tbytes += nbytes; + if (tbytes >= bufsize) /* buffer overflow */ + return (0); + } + nbytes = snprintf(buf, buflen, "%c%c", '\n', '\0'); + tbytes += nbytes; + if (tbytes >= bufsize) + return (0); + return (tbytes); +} + +/* + * Adds a nvpair, using the `name' and `value', to the nvlist in `nvl'. + * The value will be interpreted as explained at the top of this file. + */ +static void +i_ipadm_add_nvpair(nvlist_t *nvl, char *name, char *value) +{ + ipadm_conf_ent_t *ipent; + + ipent = i_ipadm_find_conf_type(name); + (*ipent->ipent_rfunc)(nvl, name, value); +} + +/* + * Adds an nvpair for IPv4 addr to the nvlist. The "name" is the string in + * IPADM_NVP_IPV4ADDR. The "value" for IPADM_NVP_IPV4ADDR is another nvlist. + * Allocate the value nvlist for IPADM_NVP_IPV4ADDR if necessary, and add + * the address and hostnames from the address object `ipaddr' to it. + * Then add the allocated nvlist to `nvl'. + */ +ipadm_status_t +i_ipadm_add_ipaddr2nvl(nvlist_t *nvl, ipadm_addrobj_t ipaddr) +{ + nvlist_t *nvl_addr = NULL; + int err; + char *name; + sa_family_t af = ipaddr->ipadm_af; + + if (af == AF_INET) { + name = IPADM_NVP_IPV4ADDR; + } else { + assert(af == AF_INET6); + name = IPADM_NVP_IPV6ADDR; + } + + if (!nvlist_exists(nvl, name)) { + if ((err = nvlist_alloc(&nvl_addr, NV_UNIQUE_NAME, 0)) != 0) + return (ipadm_errno2status(err)); + if ((err = nvlist_add_nvlist(nvl, name, nvl_addr)) != 0) { + nvlist_free(nvl_addr); + return (ipadm_errno2status(err)); + } + nvlist_free(nvl_addr); + } + if ((err = nvlist_lookup_nvlist(nvl, name, &nvl_addr)) != 0 || + (err = nvlist_add_string(nvl_addr, IPADM_NVP_IPADDRHNAME, + ipaddr->ipadm_static_aname)) != 0) + return (ipadm_errno2status(err)); + if (!sockaddrunspec(&ipaddr->ipadm_static_dst_addr)) { + if ((err = nvlist_add_string(nvl_addr, IPADM_NVP_IPDADDRHNAME, + ipaddr->ipadm_static_dname)) != 0) + return (ipadm_errno2status(err)); + } + + return (IPADM_SUCCESS); +} + +/* + * Adds an nvpair for IPv6 interface id to the nvlist. The "name" is + * the string in IPADM_NVP_INTFID. The "value" for IPADM_NVP_INTFID is another + * nvlist. Allocate the value nvlist for IPADM_NVP_INTFID if necessary, and add + * the interface id and its prefixlen from the address object `ipaddr' to it. + * Then add the allocated nvlist to `nvl'. + */ +ipadm_status_t +i_ipadm_add_intfid2nvl(nvlist_t *nvl, ipadm_addrobj_t addr) +{ + nvlist_t *nvl_addr = NULL; + struct in6_addr addr6; + int err; + + if (!nvlist_exists(nvl, IPADM_NVP_INTFID)) { + if ((err = nvlist_alloc(&nvl_addr, NV_UNIQUE_NAME, 0)) != 0) + return (ipadm_errno2status(err)); + if ((err = nvlist_add_nvlist(nvl, IPADM_NVP_INTFID, + nvl_addr)) != 0) { + nvlist_free(nvl_addr); + return (ipadm_errno2status(err)); + } + nvlist_free(nvl_addr); + } + if ((err = nvlist_lookup_nvlist(nvl, IPADM_NVP_INTFID, + &nvl_addr)) != 0 || (err = nvlist_add_uint32(nvl_addr, + IPADM_NVP_PREFIXLEN, addr->ipadm_intfidlen)) != 0) { + return (ipadm_errno2status(err)); + } + addr6 = addr->ipadm_intfid.sin6_addr; + if ((err = nvlist_add_uint8_array(nvl_addr, IPADM_NVP_IPNUMADDR, + addr6.s6_addr, 16)) != 0) { + return (ipadm_errno2status(err)); + } + if (addr->ipadm_stateless) + err = nvlist_add_string(nvl_addr, IPADM_NVP_STATELESS, "yes"); + else + err = nvlist_add_string(nvl_addr, IPADM_NVP_STATELESS, "no"); + if (err != 0) + return (ipadm_errno2status(err)); + if (addr->ipadm_stateful) + err = nvlist_add_string(nvl_addr, IPADM_NVP_STATEFUL, "yes"); + else + err = nvlist_add_string(nvl_addr, IPADM_NVP_STATEFUL, "no"); + if (err != 0) + return (ipadm_errno2status(err)); + + return (IPADM_SUCCESS); +} + +/* + * Adds an nvpair for a dhcp address object to the nvlist. The "name" is + * the string in IPADM_NVP_DHCP. The "value" for IPADM_NVP_DHCP is another + * nvlist. Allocate the value nvlist for IPADM_NVP_DHCP if necessary, and add + * the parameters from the arguments `primary' and `wait'. + * Then add the allocated nvlist to `nvl'. + */ +ipadm_status_t +i_ipadm_add_dhcp2nvl(nvlist_t *nvl, boolean_t primary, int32_t wait) +{ + nvlist_t *nvl_dhcp = NULL; + int err; + + if (!nvlist_exists(nvl, IPADM_NVP_DHCP)) { + if ((err = nvlist_alloc(&nvl_dhcp, NV_UNIQUE_NAME, 0)) != 0) + return (ipadm_errno2status(err)); + if ((err = nvlist_add_nvlist(nvl, IPADM_NVP_DHCP, + nvl_dhcp)) != 0) { + nvlist_free(nvl_dhcp); + return (ipadm_errno2status(err)); + } + nvlist_free(nvl_dhcp); + } + if ((err = nvlist_lookup_nvlist(nvl, IPADM_NVP_DHCP, &nvl_dhcp)) != 0 || + (err = nvlist_add_int32(nvl_dhcp, IPADM_NVP_WAIT, wait)) != 0 || + (err = nvlist_add_boolean_value(nvl_dhcp, IPADM_NVP_PRIMARY, + primary)) != 0) { + return (ipadm_errno2status(err)); + } + + return (IPADM_SUCCESS); +} + +/* + * Add (name, value) as an nvpair of type DATA_TYPE_STRING to nvlist. + */ +static void +i_ipadm_str_dbline2nvl(nvlist_t *nvl, char *name, char *value) +{ + /* if value is NULL create an empty node */ + if (value == NULL) + (void) nvlist_add_string(nvl, name, ""); + else + (void) nvlist_add_string(nvl, name, value); +} + +/* + * `name' = IPADM_NVP_IPV4ADDR and + * `value' = <local numeric IP string or hostname,remote numeric IP string or + * hostname> + * This function will add an nvlist with the hostname information in + * nvpairs to the nvlist in `nvl'. + */ +static void +i_ipadm_ip4_dbline2nvl(nvlist_t *nvl, char *name, char *value) +{ + char *cp, *hname; + struct ipadm_addrobj_s ipaddr; + + assert(strcmp(name, IPADM_NVP_IPV4ADDR) == 0 && value != NULL); + + bzero(&ipaddr, sizeof (ipaddr)); + ipaddr.ipadm_af = AF_INET; + + hname = value; /* local hostname */ + cp = strchr(hname, ','); + assert(cp != NULL); + *cp++ = '\0'; + (void) strlcpy(ipaddr.ipadm_static_aname, hname, + sizeof (ipaddr.ipadm_static_aname)); + + if (*cp != '\0') { + /* we have a dst hostname */ + (void) strlcpy(ipaddr.ipadm_static_dname, cp, + sizeof (ipaddr.ipadm_static_dname)); + } + (void) i_ipadm_add_ipaddr2nvl(nvl, &ipaddr); +} + +/* + * `name' = IPADM_NVP_IPV6ADDR and + * `value' = <local numeric IP string or hostname,remote numeric IP string or + * hostname> + * This function will add an nvlist with the hostname information in + * nvpairs to the nvlist in `nvl'. + */ +static void +i_ipadm_ip6_dbline2nvl(nvlist_t *nvl, char *name, char *value) +{ + char *cp, *hname; + struct ipadm_addrobj_s ipaddr; + + assert(strcmp(name, IPADM_NVP_IPV6ADDR) == 0 && value != NULL); + + bzero(&ipaddr, sizeof (ipaddr)); + ipaddr.ipadm_af = AF_INET6; + + hname = value; /* local hostname */ + cp = strchr(hname, ','); + assert(cp != NULL); + *cp++ = '\0'; + (void) strlcpy(ipaddr.ipadm_static_aname, hname, + sizeof (ipaddr.ipadm_static_aname)); + + if (*cp != '\0') { + /* we have a dst hostname */ + (void) strlcpy(ipaddr.ipadm_static_dname, cp, + sizeof (ipaddr.ipadm_static_dname)); + } + (void) i_ipadm_add_ipaddr2nvl(nvl, &ipaddr); +} + +/* + * `name' = IPADM_NVP_INTFID and `value' = <intfid/prefixlen>,{yes,no},{yes|no} + * This function will add an nvlist with the address object information in + * nvpairs to the nvlist in `nvl'. + */ +static void +i_ipadm_intfid_dbline2nvl(nvlist_t *nvl, char *name, char *value) +{ + char *cp; + struct ipadm_addrobj_s ipaddr; + char *endp; + char *prefixlen; + char *stateless; + char *stateful; + + assert(strcmp(name, IPADM_NVP_INTFID) == 0 && value != NULL); + + bzero(&ipaddr, sizeof (ipaddr)); + + cp = strchr(value, '/'); + assert(cp != NULL); + + *cp++ = '\0'; + ipaddr.ipadm_intfid.sin6_family = AF_INET6; + (void) inet_pton(AF_INET6, value, &ipaddr.ipadm_intfid.sin6_addr); + + prefixlen = cp; + cp = strchr(cp, ','); + assert(cp != NULL); + *cp++ = '\0'; + + errno = 0; + ipaddr.ipadm_intfidlen = (uint32_t)strtoul(prefixlen, &endp, 10); + if (*endp != '\0' || errno != 0) + return; + + stateless = cp; + stateful = strchr(stateless, ','); + assert(stateful != NULL); + *stateful++ = '\0'; + ipaddr.ipadm_stateless = (strcmp(stateless, "yes") == 0); + ipaddr.ipadm_stateful = (strcmp(stateful, "yes") == 0); + + /* Add all of it to the given nvlist */ + (void) i_ipadm_add_intfid2nvl(nvl, &ipaddr); +} + +/* + * `name' = IPADM_NVP_DHCP and `value' = <wait_time>,{yes|no} + * This function will add an nvlist with the dhcp address object information in + * nvpairs to the nvlist in `nvl'. + */ +static void +i_ipadm_dhcp_dbline2nvl(nvlist_t *nvl, char *name, char *value) +{ + char *cp; + char *endp; + long wait_time; + boolean_t primary; + + assert(strcmp(name, IPADM_NVP_DHCP) == 0 && value != NULL); + cp = strchr(value, ','); + assert(cp != NULL); + *cp++ = '\0'; + errno = 0; + wait_time = strtol(value, &endp, 10); + if (*endp != '\0' || errno != 0) + return; + primary = (strcmp(cp, "yes") == 0); + (void) i_ipadm_add_dhcp2nvl(nvl, primary, (int32_t)wait_time); +} + +/* + * Parses the buffer, for name-value pairs and creates nvlist. The value + * is always considered to be a string. + */ +int +ipadm_str2nvlist(const char *inbuf, nvlist_t **ipnvl, uint_t flags) +{ + char *nv, *name, *val, *buf, *cp, *sep; + int err; + + if (inbuf == NULL || inbuf[0] == '\0' || ipnvl == NULL) + return (EINVAL); + *ipnvl = NULL; + + /* + * If IPADM_NORVAL is set, then `inbuf' should be comma delimited values + */ + if ((flags & IPADM_NORVAL) && strchr(inbuf, '=') != NULL) + return (EINVAL); + + if ((cp = buf = strdup(inbuf)) == NULL) + return (errno); + + while (isspace(*buf)) + buf++; + + if (*buf == '\0') { + err = EINVAL; + goto fail; + } + + nv = buf; + /* + * work on one nvpair at a time and extract the name and value + */ + sep = ((flags & IPADM_NORVAL) ? IPADM_NAME_SEP : IPADM_NVPAIR_SEP); + while ((nv = strsep(&buf, sep)) != NULL) { + if (*nv == '\n') + continue; + name = nv; + if ((val = strchr(nv, '=')) != NULL) + *val++ = '\0'; + if (*ipnvl == NULL && + (err = nvlist_alloc(ipnvl, NV_UNIQUE_NAME, 0)) != 0) + goto fail; + if (nvlist_exists(*ipnvl, name)) { + err = EEXIST; + goto fail; + } + /* Add the extracted nvpair to the nvlist `ipnvl'. */ + (void) i_ipadm_add_nvpair(*ipnvl, name, val); + } + free(cp); + return (0); +fail: + free(cp); + nvlist_free(*ipnvl); + *ipnvl = NULL; + return (err); +} + +/* + * Opens the data store for read/write operation. For write operation we open + * another file and scribble the changes to it and copy the new file back to + * old file. + */ +int +ipadm_rw_db(db_wfunc_t *db_walk_func, void *arg, const char *db_file, + mode_t db_perms, ipadm_db_op_t db_op) +{ + FILE *fp, *nfp = NULL; + char file[MAXPATHLEN]; + char newfile[MAXPATHLEN]; + int nfd; + boolean_t writeop; + int err = 0; + + writeop = (db_op != IPADM_DB_READ); + + (void) snprintf(file, MAXPATHLEN, "%s/%s", ipadm_rootdir, db_file); + + /* open the data store */ + if ((fp = fopen(file, (writeop ? "r+" : "r"))) == NULL) + return (errno); + + if (writeop) { + (void) snprintf(newfile, MAXPATHLEN, "%s/%s.new", + ipadm_rootdir, db_file); + if ((nfd = open(newfile, O_WRONLY | O_CREAT | O_TRUNC, + db_perms)) < 0) { + err = errno; + (void) fclose(fp); + return (err); + } + + if ((nfp = fdopen(nfd, "w")) == NULL) { + err = errno; + (void) close(nfd); + (void) fclose(fp); + (void) unlink(newfile); + return (err); + } + } + err = ipadm_process_db_line(db_walk_func, arg, fp, nfp, db_op); + if (!writeop) + goto done; + if (err != 0 && err != ENOENT) + goto done; + + if (fflush(nfp) == EOF) { + err = errno; + goto done; + } + (void) fclose(fp); + (void) fclose(nfp); + + if (rename(newfile, file) < 0) { + err = errno; + (void) unlink(newfile); + } + return (err); +done: + if (nfp != NULL) { + (void) fclose(nfp); + if (err != 0) + (void) unlink(newfile); + } + (void) fclose(fp); + return (err); +} + +/* + * Processes each line of the configuration file, skipping lines with + * leading spaces, blank lines and comments. The line form the DB + * is converted to nvlist and the callback function is called to process + * the list. The buf could be modified by the callback function and + * if this is a write operation and buf is not truncated, buf will + * be written to disk. + * + * Further if cont is set to B_FALSE, the remainder of the file will + * continue to be read (however callback function will not be called) and, + * if necessary, written to disk as well. + */ +static int +ipadm_process_db_line(db_wfunc_t *db_walk_func, void *arg, FILE *fp, FILE *nfp, + ipadm_db_op_t db_op) +{ + int err = 0; + char buf[MAXLINELEN]; + boolean_t cont = B_TRUE; + int i, len; + nvlist_t *db_nvl = NULL; + boolean_t line_deleted = B_FALSE; + + while (fgets(buf, MAXLINELEN, fp) != NULL) { + /* + * Skip leading spaces, blank lines, and comments. + */ + len = strnlen(buf, MAXLINELEN); + for (i = 0; i < len; i++) { + if (!isspace(buf[i])) + break; + } + + if (i != len && buf[i] != '#' && cont) { + if (ipadm_str2nvlist(buf, &db_nvl, 0) == 0) { + cont = db_walk_func(arg, db_nvl, buf, + MAXLINELEN, &err); + } else { + /* Delete corrupted line. */ + buf[0] = '\0'; + } + nvlist_free(db_nvl); + db_nvl = NULL; + } + if (err != 0) + break; + if (nfp != NULL && buf[0] == '\0') + line_deleted = B_TRUE; + if (nfp != NULL && buf[0] != '\0' && fputs(buf, nfp) == EOF) { + err = errno; + break; + } + } + + if (err != 0 || !cont) + return (err); + + if (db_op == IPADM_DB_WRITE) { + ipadm_dbwrite_cbarg_t *cb = arg; + nvlist_t *nvl = cb->dbw_nvl; + + /* + * If the specified entry is not found above, we add + * the entry to the configuration file, here. + */ + (void) memset(buf, 0, MAXLINELEN); + if (ipadm_nvlist2str(nvl, buf, MAXLINELEN) == 0) + err = ENOBUFS; + else if (fputs(buf, nfp) == EOF) + err = errno; + return (err); + } + + if (db_op == IPADM_DB_DELETE && line_deleted) + return (0); + + /* if we have come this far, then we didn't find any match */ + return (ENOENT); +} diff --git a/usr/src/lib/libipadm/common/ipadm_prop.c b/usr/src/lib/libipadm/common/ipadm_prop.c new file mode 100644 index 0000000000..56aba6bb2d --- /dev/null +++ b/usr/src/lib/libipadm/common/ipadm_prop.c @@ -0,0 +1,1699 @@ +/* + * 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. + */ + +/* + * This file contains routines that are used to modify/retrieve protocol or + * interface property values. It also holds all the supported properties for + * both IP interface and protocols in `ipadm_prop_desc_t'. Following protocols + * are supported: IP, IPv4, IPv6, TCP, SCTP, UDP and ICMP. + * + * This file also contains walkers, which walks through the property table and + * calls the callback function, of the form `ipadm_prop_wfunc_t' , for every + * property in the table. + */ + +#include <unistd.h> +#include <errno.h> +#include <ctype.h> +#include <fcntl.h> +#include <strings.h> +#include <stdlib.h> +#include <netinet/in.h> +#include <arpa/inet.h> +#include <sys/sockio.h> +#include <assert.h> +#include <libdllink.h> +#include <zone.h> +#include "libipadm_impl.h" + +#define IPADM_NONESTR "none" +#define DEF_METRIC_VAL 0 /* default metric value */ + +#define A_CNT(arr) (sizeof (arr) / sizeof (arr[0])) + +static ipadm_status_t i_ipadm_validate_if(ipadm_handle_t, const char *, + uint_t, uint_t); + +/* + * Callback functions to retrieve property values from the kernel. These + * functions, when required, translate the values from the kernel to a format + * suitable for printing. For example: boolean values will be translated + * to on/off. They also retrieve DEFAULT, PERM and POSSIBLE values for + * a given property. + */ +static ipadm_pd_getf_t i_ipadm_get_prop, i_ipadm_get_ifprop_flags, + i_ipadm_get_mtu, i_ipadm_get_metric, + i_ipadm_get_usesrc, i_ipadm_get_forwarding, + i_ipadm_get_ecnsack; + +/* + * Callback function to set property values. These functions translate the + * values to a format suitable for kernel consumption, allocates the necessary + * ioctl buffers and then invokes ioctl(). + */ +static ipadm_pd_setf_t i_ipadm_set_prop, i_ipadm_set_mtu, + i_ipadm_set_ifprop_flags, + i_ipadm_set_metric, i_ipadm_set_usesrc, + i_ipadm_set_forwarding, i_ipadm_set_eprivport, + i_ipadm_set_ecnsack; + +/* array of protocols we support */ +static int protocols[] = { MOD_PROTO_IP, MOD_PROTO_RAWIP, + MOD_PROTO_TCP, MOD_PROTO_UDP, + MOD_PROTO_SCTP }; + +/* + * Supported IP protocol properties. + */ +static ipadm_prop_desc_t ipadm_ip_prop_table[] = { + { "arp", IPADMPROP_CLASS_IF, MOD_PROTO_IPV4, + i_ipadm_set_ifprop_flags, i_ipadm_get_onoff, + i_ipadm_get_ifprop_flags }, + + { "forwarding", IPADMPROP_CLASS_MODIF, MOD_PROTO_IPV4, + i_ipadm_set_forwarding, i_ipadm_get_onoff, + i_ipadm_get_forwarding }, + + { "metric", IPADMPROP_CLASS_IF, MOD_PROTO_IPV4, + i_ipadm_set_metric, NULL, i_ipadm_get_metric }, + + { "mtu", IPADMPROP_CLASS_IF, MOD_PROTO_IPV4, + i_ipadm_set_mtu, i_ipadm_get_mtu, i_ipadm_get_mtu }, + + { "exchange_routes", IPADMPROP_CLASS_IF, MOD_PROTO_IPV4, + i_ipadm_set_ifprop_flags, i_ipadm_get_onoff, + i_ipadm_get_ifprop_flags }, + + { "usesrc", IPADMPROP_CLASS_IF, MOD_PROTO_IPV4, + i_ipadm_set_usesrc, NULL, i_ipadm_get_usesrc }, + + { "ttl", IPADMPROP_CLASS_MODULE, MOD_PROTO_IPV4, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "forwarding", IPADMPROP_CLASS_MODIF, MOD_PROTO_IPV6, + i_ipadm_set_forwarding, i_ipadm_get_onoff, + i_ipadm_get_forwarding }, + + { "hoplimit", IPADMPROP_CLASS_MODULE, MOD_PROTO_IPV6, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "metric", IPADMPROP_CLASS_IF, MOD_PROTO_IPV6, + i_ipadm_set_metric, NULL, i_ipadm_get_metric }, + + { "mtu", IPADMPROP_CLASS_IF, MOD_PROTO_IPV6, + i_ipadm_set_mtu, i_ipadm_get_mtu, i_ipadm_get_mtu }, + + { "nud", IPADMPROP_CLASS_IF, MOD_PROTO_IPV6, + i_ipadm_set_ifprop_flags, i_ipadm_get_onoff, + i_ipadm_get_ifprop_flags }, + + { "exchange_routes", IPADMPROP_CLASS_IF, MOD_PROTO_IPV6, + i_ipadm_set_ifprop_flags, i_ipadm_get_onoff, + i_ipadm_get_ifprop_flags }, + + { "usesrc", IPADMPROP_CLASS_IF, MOD_PROTO_IPV6, + i_ipadm_set_usesrc, NULL, i_ipadm_get_usesrc }, + + { NULL, 0, 0, NULL, NULL, NULL } +}; + +/* possible values for TCP properties `ecn' and `sack' */ +static const char *ecn_sack_vals[] = {"never", "passive", "active", NULL}; + +/* Supported TCP protocol properties */ +static ipadm_prop_desc_t ipadm_tcp_prop_table[] = { + { "ecn", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_ecnsack, i_ipadm_get_ecnsack, i_ipadm_get_ecnsack }, + + { "extra_priv_ports", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_eprivport, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "largest_anon_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "recv_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "sack", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_ecnsack, i_ipadm_get_ecnsack, i_ipadm_get_ecnsack }, + + { "send_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "smallest_anon_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "smallest_nonpriv_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_TCP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { NULL, 0, 0, NULL, NULL, NULL } +}; + +/* Supported UDP protocol properties */ +static ipadm_prop_desc_t ipadm_udp_prop_table[] = { + { "extra_priv_ports", IPADMPROP_CLASS_MODULE, MOD_PROTO_UDP, + i_ipadm_set_eprivport, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "largest_anon_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_UDP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "recv_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_UDP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "send_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_UDP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "smallest_anon_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_UDP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "smallest_nonpriv_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_UDP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { NULL, 0, 0, NULL, NULL, NULL } +}; + +/* Supported SCTP protocol properties */ +static ipadm_prop_desc_t ipadm_sctp_prop_table[] = { + { "extra_priv_ports", IPADMPROP_CLASS_MODULE, MOD_PROTO_SCTP, + i_ipadm_set_eprivport, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "largest_anon_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_SCTP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "recv_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_SCTP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "send_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_SCTP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "smallest_anon_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_SCTP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "smallest_nonpriv_port", IPADMPROP_CLASS_MODULE, MOD_PROTO_SCTP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { NULL, 0, 0, NULL, NULL, NULL } +}; + +/* Supported ICMP protocol properties */ +static ipadm_prop_desc_t ipadm_icmp_prop_table[] = { + { "recv_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_RAWIP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { "send_maxbuf", IPADMPROP_CLASS_MODULE, MOD_PROTO_RAWIP, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }, + + { NULL, 0, 0, NULL, NULL, NULL } +}; + +/* + * A dummy private property structure, used while handling private + * protocol properties (properties not yet supported by libipadm). + */ +static ipadm_prop_desc_t ipadm_privprop =\ + { NULL, IPADMPROP_CLASS_MODULE, MOD_PROTO_NONE, + i_ipadm_set_prop, i_ipadm_get_prop, i_ipadm_get_prop }; + +/* + * Returns the property description table, for the given protocol + */ +static ipadm_prop_desc_t * +i_ipadm_get_propdesc_table(uint_t proto) +{ + switch (proto) { + case MOD_PROTO_IP: + case MOD_PROTO_IPV4: + case MOD_PROTO_IPV6: + return (ipadm_ip_prop_table); + case MOD_PROTO_RAWIP: + return (ipadm_icmp_prop_table); + case MOD_PROTO_TCP: + return (ipadm_tcp_prop_table); + case MOD_PROTO_UDP: + return (ipadm_udp_prop_table); + case MOD_PROTO_SCTP: + return (ipadm_sctp_prop_table); + } + + return (NULL); +} + +char * +ipadm_proto2str(uint_t proto) +{ + switch (proto) { + case MOD_PROTO_IP: + return ("ip"); + case MOD_PROTO_IPV4: + return ("ipv4"); + case MOD_PROTO_IPV6: + return ("ipv6"); + case MOD_PROTO_RAWIP: + return ("icmp"); + case MOD_PROTO_TCP: + return ("tcp"); + case MOD_PROTO_UDP: + return ("udp"); + case MOD_PROTO_SCTP: + return ("sctp"); + } + + return (NULL); +} + +uint_t +ipadm_str2proto(const char *protostr) +{ + if (protostr == NULL) + return (MOD_PROTO_NONE); + if (strcmp(protostr, "tcp") == 0) + return (MOD_PROTO_TCP); + else if (strcmp(protostr, "udp") == 0) + return (MOD_PROTO_UDP); + else if (strcmp(protostr, "ip") == 0) + return (MOD_PROTO_IP); + else if (strcmp(protostr, "ipv4") == 0) + return (MOD_PROTO_IPV4); + else if (strcmp(protostr, "ipv6") == 0) + return (MOD_PROTO_IPV6); + else if (strcmp(protostr, "icmp") == 0) + return (MOD_PROTO_RAWIP); + else if (strcmp(protostr, "sctp") == 0) + return (MOD_PROTO_SCTP); + else if (strcmp(protostr, "arp") == 0) + return (MOD_PROTO_IP); + + return (MOD_PROTO_NONE); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_mtu(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + struct lifreq lifr; + char *endp; + uint_t mtu; + int s; + const char *ifname = arg; + char val[MAXPROPVALLEN]; + + /* to reset MTU first retrieve the default MTU and then set it */ + if (flags & IPADM_OPT_DEFAULT) { + ipadm_status_t status; + uint_t size = MAXPROPVALLEN; + + status = i_ipadm_get_prop(iph, arg, pdp, val, &size, + proto, MOD_PROP_DEFAULT); + if (status != IPADM_SUCCESS) + return (status); + pval = val; + } + + errno = 0; + mtu = (uint_t)strtol(pval, &endp, 10); + if (errno != 0 || *endp != '\0') + return (IPADM_INVALID_ARG); + + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + lifr.lifr_mtu = mtu; + + s = (proto == MOD_PROTO_IPV6 ? iph->iph_sock6 : iph->iph_sock); + if (ioctl(s, SIOCSLIFMTU, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + + return (IPADM_SUCCESS); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_metric(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + struct lifreq lifr; + char *endp; + int metric; + const char *ifname = arg; + int s; + + /* if we are resetting, set the value to its default value */ + if (flags & IPADM_OPT_DEFAULT) { + metric = DEF_METRIC_VAL; + } else { + errno = 0; + metric = (uint_t)strtol(pval, &endp, 10); + if (errno != 0 || *endp != '\0') + return (IPADM_INVALID_ARG); + } + + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + lifr.lifr_metric = metric; + + s = (proto == MOD_PROTO_IPV6 ? iph->iph_sock6 : iph->iph_sock); + + if (ioctl(s, SIOCSLIFMETRIC, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + + return (IPADM_SUCCESS); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_usesrc(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + struct lifreq lifr; + const char *ifname = arg; + int s; + uint_t ifindex = 0; + + /* if we are resetting, set the value to its default value */ + if (flags & IPADM_OPT_DEFAULT) + pval = IPADM_NONESTR; + + /* + * cannot specify logical interface name. We can also filter out other + * bogus interface names here itself through i_ipadm_validate_ifname(). + */ + if (strcmp(pval, IPADM_NONESTR) != 0 && + !i_ipadm_validate_ifname(iph, pval)) + return (IPADM_INVALID_ARG); + + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + + s = (proto == MOD_PROTO_IPV6 ? iph->iph_sock6 : iph->iph_sock); + + if (strcmp(pval, IPADM_NONESTR) != 0) { + if ((ifindex = if_nametoindex(pval)) == 0) + return (ipadm_errno2status(errno)); + lifr.lifr_index = ifindex; + } else { + if (ioctl(s, SIOCGLIFUSESRC, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + lifr.lifr_index = 0; + } + if (ioctl(s, SIOCSLIFUSESRC, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + + return (IPADM_SUCCESS); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_ifprop_flags(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + ipadm_status_t status = IPADM_SUCCESS; + const char *ifname = arg; + uint64_t on_flags = 0, off_flags = 0; + boolean_t on = B_FALSE; + sa_family_t af = (proto == MOD_PROTO_IPV6 ? AF_INET6 : AF_INET); + + /* if we are resetting, set the value to its default value */ + if (flags & IPADM_OPT_DEFAULT) { + if (strcmp(pdp->ipd_name, "exchange_routes") == 0 || + strcmp(pdp->ipd_name, "arp") == 0 || + strcmp(pdp->ipd_name, "nud") == 0) { + pval = IPADM_ONSTR; + } else if (strcmp(pdp->ipd_name, "forwarding") == 0) { + pval = IPADM_OFFSTR; + } else { + return (IPADM_PROP_UNKNOWN); + } + } + + if (strcmp(pval, IPADM_ONSTR) == 0) + on = B_TRUE; + else if (strcmp(pval, IPADM_OFFSTR) == 0) + on = B_FALSE; + else + return (IPADM_INVALID_ARG); + + if (strcmp(pdp->ipd_name, "exchange_routes") == 0) { + if (on) + off_flags = IFF_NORTEXCH; + else + on_flags = IFF_NORTEXCH; + } else if (strcmp(pdp->ipd_name, "arp") == 0) { + if (on) + off_flags = IFF_NOARP; + else + on_flags = IFF_NOARP; + } else if (strcmp(pdp->ipd_name, "nud") == 0) { + if (on) + off_flags = IFF_NONUD; + else + on_flags = IFF_NONUD; + } else if (strcmp(pdp->ipd_name, "forwarding") == 0) { + if (on) + on_flags = IFF_ROUTER; + else + off_flags = IFF_ROUTER; + } + + if (on_flags || off_flags) { + status = i_ipadm_set_flags(iph, ifname, af, on_flags, + off_flags); + } + return (status); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_eprivport(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + nvlist_t *portsnvl = NULL; + nvpair_t *nvp; + ipadm_status_t status = IPADM_SUCCESS; + int err; + char *port; + uint_t count = 0; + + if (flags & IPADM_OPT_DEFAULT) { + assert(pval == NULL); + return (i_ipadm_set_prop(iph, arg, pdp, pval, proto, flags)); + } + + if ((err = ipadm_str2nvlist(pval, &portsnvl, IPADM_NORVAL)) != 0) + return (ipadm_errno2status(err)); + + /* count the number of ports */ + for (nvp = nvlist_next_nvpair(portsnvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(portsnvl, nvp)) { + ++count; + } + + /* We allow only one port to be added or removed, at a time */ + if (count > 1 && (flags & (IPADM_OPT_APPEND|IPADM_OPT_REMOVE))) + return (IPADM_INVALID_ARG); + + /* + * However on reboot, while initializing protocol properties, + * extra_priv_ports might have multiple values. Only in that case + * we allow setting multiple properties. + */ + if (count > 1 && !(iph->iph_flags & IPH_INIT)) + return (IPADM_INVALID_ARG); + + count = 0; + for (nvp = nvlist_next_nvpair(portsnvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(portsnvl, nvp)) { + port = nvpair_name(nvp); + if (count == 0) { + status = i_ipadm_set_prop(iph, arg, pdp, port, proto, + flags); + } else { + assert(iph->iph_flags & IPH_INIT); + status = i_ipadm_set_prop(iph, arg, pdp, port, proto, + IPADM_OPT_APPEND); + } + ++count; + if (status != IPADM_SUCCESS) + break; + } +ret: + nvlist_free(portsnvl); + return (status); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_forwarding(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + const char *ifname = arg; + ipadm_status_t status; + + /* + * if interface name is provided, then set forwarding using the + * IFF_ROUTER flag + */ + if (ifname != NULL) { + status = i_ipadm_set_ifprop_flags(iph, ifname, pdp, pval, + proto, flags); + } else { + char *val = NULL; + + /* + * if the caller is IPH_LEGACY, `pval' already contains + * numeric values. + */ + if (!(flags & IPADM_OPT_DEFAULT) && + !(iph->iph_flags & IPH_LEGACY)) { + + if (strcmp(pval, IPADM_ONSTR) == 0) + val = "1"; + else if (strcmp(pval, IPADM_OFFSTR) == 0) + val = "0"; + else + return (IPADM_INVALID_ARG); + pval = val; + } + + status = i_ipadm_set_prop(iph, ifname, pdp, pval, proto, flags); + } + + return (status); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_set_ecnsack(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + uint_t i; + char val[MAXPROPVALLEN]; + + /* if IPH_LEGACY is set, `pval' already contains numeric values */ + if (!(flags & IPADM_OPT_DEFAULT) && !(iph->iph_flags & IPH_LEGACY)) { + for (i = 0; ecn_sack_vals[i] != NULL; i++) { + if (strcmp(pval, ecn_sack_vals[i]) == 0) + break; + } + if (ecn_sack_vals[i] == NULL) + return (IPADM_INVALID_ARG); + (void) snprintf(val, MAXPROPVALLEN, "%d", i); + pval = val; + } + + return (i_ipadm_set_prop(iph, arg, pdp, pval, proto, flags)); +} + +/* ARGSUSED */ +ipadm_status_t +i_ipadm_get_ecnsack(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + ipadm_status_t status = IPADM_SUCCESS; + uint_t i, nbytes = 0; + + switch (valtype) { + case MOD_PROP_POSSIBLE: + for (i = 0; ecn_sack_vals[i] != NULL; i++) { + if (i == 0) + nbytes += snprintf(buf + nbytes, + *bufsize - nbytes, "%s", ecn_sack_vals[i]); + else + nbytes += snprintf(buf + nbytes, + *bufsize - nbytes, ",%s", ecn_sack_vals[i]); + if (nbytes >= *bufsize) + break; + } + break; + case MOD_PROP_PERM: + case MOD_PROP_DEFAULT: + case MOD_PROP_ACTIVE: + status = i_ipadm_get_prop(iph, arg, pdp, buf, bufsize, proto, + valtype); + + /* + * If IPH_LEGACY is set, do not convert the value returned + * from kernel, + */ + if (iph->iph_flags & IPH_LEGACY) + break; + + /* + * For current and default value, convert the value returned + * from kernel to more discrete representation. + */ + if (status == IPADM_SUCCESS && (valtype == MOD_PROP_ACTIVE || + valtype == MOD_PROP_DEFAULT)) { + i = atoi(buf); + assert(i < 3); + nbytes = snprintf(buf, *bufsize, "%s", + ecn_sack_vals[i]); + } + break; + default: + return (IPADM_INVALID_ARG); + } + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + + return (status); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_forwarding(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + const char *ifname = arg; + ipadm_status_t status = IPADM_SUCCESS; + + /* + * if interface name is provided, then get forwarding status using + * SIOCGLIFFLAGS + */ + if (ifname != NULL) { + status = i_ipadm_get_ifprop_flags(iph, ifname, pdp, + buf, bufsize, pdp->ipd_proto, valtype); + } else { + status = i_ipadm_get_prop(iph, ifname, pdp, buf, + bufsize, proto, valtype); + /* + * If IPH_LEGACY is set, do not convert the value returned + * from kernel, + */ + if (iph->iph_flags & IPH_LEGACY) + goto ret; + if (status == IPADM_SUCCESS && (valtype == MOD_PROP_ACTIVE || + valtype == MOD_PROP_DEFAULT)) { + uint_t val = atoi(buf); + + (void) snprintf(buf, *bufsize, + (val == 1 ? IPADM_ONSTR : IPADM_OFFSTR)); + } + } + +ret: + return (status); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_mtu(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + struct lifreq lifr; + const char *ifname = arg; + size_t nbytes; + int s; + + switch (valtype) { + case MOD_PROP_PERM: + nbytes = snprintf(buf, *bufsize, "%d", MOD_PROP_PERM_RW); + break; + case MOD_PROP_DEFAULT: + case MOD_PROP_POSSIBLE: + return (i_ipadm_get_prop(iph, arg, pdp, buf, bufsize, + proto, valtype)); + case MOD_PROP_ACTIVE: + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + s = (proto == MOD_PROTO_IPV6 ? iph->iph_sock6 : iph->iph_sock); + + if (ioctl(s, SIOCGLIFMTU, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + nbytes = snprintf(buf, *bufsize, "%u", lifr.lifr_mtu); + break; + default: + return (IPADM_INVALID_ARG); + } + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + return (IPADM_SUCCESS); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_metric(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + struct lifreq lifr; + const char *ifname = arg; + size_t nbytes; + int s, val; + + switch (valtype) { + case MOD_PROP_PERM: + val = MOD_PROP_PERM_RW; + break; + case MOD_PROP_DEFAULT: + val = DEF_METRIC_VAL; + break; + case MOD_PROP_ACTIVE: + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + + s = (proto == MOD_PROTO_IPV6 ? iph->iph_sock6 : iph->iph_sock); + if (ioctl(s, SIOCGLIFMETRIC, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + val = lifr.lifr_metric; + break; + default: + return (IPADM_INVALID_ARG); + } + nbytes = snprintf(buf, *bufsize, "%d", val); + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + + return (IPADM_SUCCESS); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_usesrc(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *ipd, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + struct lifreq lifr; + const char *ifname = arg; + int s; + char if_name[IF_NAMESIZE]; + size_t nbytes; + + switch (valtype) { + case MOD_PROP_PERM: + nbytes = snprintf(buf, *bufsize, "%d", MOD_PROP_PERM_RW); + break; + case MOD_PROP_DEFAULT: + nbytes = snprintf(buf, *bufsize, "%s", IPADM_NONESTR); + break; + case MOD_PROP_ACTIVE: + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + + s = (proto == MOD_PROTO_IPV6 ? iph->iph_sock6 : iph->iph_sock); + if (ioctl(s, SIOCGLIFUSESRC, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + if (lifr.lifr_index == 0) { + /* no src address was set, so print 'none' */ + (void) strlcpy(if_name, IPADM_NONESTR, + sizeof (if_name)); + } else if (if_indextoname(lifr.lifr_index, if_name) == NULL) { + return (ipadm_errno2status(errno)); + } + nbytes = snprintf(buf, *bufsize, "%s", if_name); + break; + default: + return (IPADM_INVALID_ARG); + } + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + return (IPADM_NO_BUFS); + } + return (IPADM_SUCCESS); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_ifprop_flags(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + uint64_t intf_flags; + char *val; + size_t nbytes; + const char *ifname = arg; + sa_family_t af; + ipadm_status_t status = IPADM_SUCCESS; + + switch (valtype) { + case MOD_PROP_PERM: + nbytes = snprintf(buf, *bufsize, "%d", MOD_PROP_PERM_RW); + break; + case MOD_PROP_DEFAULT: + if (strcmp(pdp->ipd_name, "exchange_routes") == 0 || + strcmp(pdp->ipd_name, "arp") == 0 || + strcmp(pdp->ipd_name, "nud") == 0) { + val = IPADM_ONSTR; + } else if (strcmp(pdp->ipd_name, "forwarding") == 0) { + val = IPADM_OFFSTR; + } else { + return (IPADM_PROP_UNKNOWN); + } + nbytes = snprintf(buf, *bufsize, "%s", val); + break; + case MOD_PROP_ACTIVE: + af = (proto == MOD_PROTO_IPV6 ? AF_INET6 : AF_INET); + status = i_ipadm_get_flags(iph, ifname, af, &intf_flags); + if (status != IPADM_SUCCESS) + return (status); + + val = IPADM_OFFSTR; + if (strcmp(pdp->ipd_name, "exchange_routes") == 0) { + if (!(intf_flags & IFF_NORTEXCH)) + val = IPADM_ONSTR; + } else if (strcmp(pdp->ipd_name, "forwarding") == 0) { + if (intf_flags & IFF_ROUTER) + val = IPADM_ONSTR; + } else if (strcmp(pdp->ipd_name, "arp") == 0) { + if (!(intf_flags & IFF_NOARP)) + val = IPADM_ONSTR; + } else if (strcmp(pdp->ipd_name, "nud") == 0) { + if (!(intf_flags & IFF_NONUD)) + val = IPADM_ONSTR; + } + nbytes = snprintf(buf, *bufsize, "%s", val); + break; + default: + return (IPADM_INVALID_ARG); + } + if (nbytes >= *bufsize) { + /* insufficient buffer space */ + *bufsize = nbytes + 1; + status = IPADM_NO_BUFS; + } + + return (status); +} + +static void +i_ipadm_perm2str(char *buf, uint_t *bufsize) +{ + uint_t perm = atoi(buf); + + (void) snprintf(buf, *bufsize, "%c%c", + ((perm & MOD_PROP_PERM_READ) != 0) ? 'r' : '-', + ((perm & MOD_PROP_PERM_WRITE) != 0) ? 'w' : '-'); +} + +/* ARGSUSED */ +static ipadm_status_t +i_ipadm_get_prop(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + ipadm_status_t status = IPADM_SUCCESS; + const char *ifname = arg; + mod_ioc_prop_t *mip; + char *pname = pdp->ipd_name; + uint_t iocsize; + + /* allocate sufficient ioctl buffer to retrieve value */ + iocsize = sizeof (mod_ioc_prop_t) + *bufsize - 1; + if ((mip = calloc(1, iocsize)) == NULL) + return (IPADM_NO_BUFS); + + mip->mpr_version = MOD_PROP_VERSION; + mip->mpr_flags = valtype; + mip->mpr_proto = proto; + if (ifname != NULL) { + (void) strlcpy(mip->mpr_ifname, ifname, + sizeof (mip->mpr_ifname)); + } + (void) strlcpy(mip->mpr_name, pname, sizeof (mip->mpr_name)); + mip->mpr_valsize = *bufsize; + + if (i_ipadm_strioctl(iph->iph_sock, SIOCGETPROP, (char *)mip, + iocsize) < 0) { + if (errno == ENOENT) + status = IPADM_PROP_UNKNOWN; + else + status = ipadm_errno2status(errno); + } else { + bcopy(mip->mpr_val, buf, *bufsize); + } + + free(mip); + return (status); +} + +/* + * populates the ipmgmt_prop_arg_t based on the class of property. + */ +static void +i_ipadm_populate_proparg(ipmgmt_prop_arg_t *pargp, ipadm_prop_desc_t *pdp, + const char *pval, const void *object) +{ + const struct ipadm_addrobj_s *ipaddr; + uint_t class = pdp->ipd_class; + uint_t proto = pdp->ipd_proto; + + (void) strlcpy(pargp->ia_pname, pdp->ipd_name, + sizeof (pargp->ia_pname)); + if (pval != NULL) + (void) strlcpy(pargp->ia_pval, pval, sizeof (pargp->ia_pval)); + + switch (class) { + case IPADMPROP_CLASS_MODULE: + (void) strlcpy(pargp->ia_module, object, + sizeof (pargp->ia_module)); + break; + case IPADMPROP_CLASS_MODIF: + /* check if object is protostr or an ifname */ + if (ipadm_str2proto(object) != MOD_PROTO_NONE) { + (void) strlcpy(pargp->ia_module, object, + sizeof (pargp->ia_module)); + break; + } + /* it's an interface property, fall through */ + /* FALLTHRU */ + case IPADMPROP_CLASS_IF: + (void) strlcpy(pargp->ia_ifname, object, + sizeof (pargp->ia_ifname)); + (void) strlcpy(pargp->ia_module, ipadm_proto2str(proto), + sizeof (pargp->ia_module)); + break; + case IPADMPROP_CLASS_ADDR: + ipaddr = object; + (void) strlcpy(pargp->ia_ifname, ipaddr->ipadm_ifname, + sizeof (pargp->ia_ifname)); + (void) strlcpy(pargp->ia_aobjname, ipaddr->ipadm_aobjname, + sizeof (pargp->ia_aobjname)); + break; + } +} + +/* + * Common function to retrieve property value for a given interface `ifname' or + * for a given protocol `proto'. The property name is in `pname'. + * + * `valtype' determines the type of value that will be retrieved. + * IPADM_OPT_ACTIVE - current value of the property (active config) + * IPADM_OPT_PERSIST - value of the property from persistent store + * IPADM_OPT_DEFAULT - default hard coded value (boot-time value) + * IPADM_OPT_PERM - read/write permissions for the value + * IPADM_OPT_POSSIBLE - range of values + */ +static ipadm_status_t +i_ipadm_getprop_common(ipadm_handle_t iph, const char *ifname, + const char *pname, char *buf, uint_t *bufsize, uint_t proto, + uint_t valtype) +{ + ipadm_status_t status = IPADM_SUCCESS; + ipadm_prop_desc_t *pdp, *pdtbl; + char priv_propname[MAXPROPNAMELEN]; + boolean_t matched_name = B_FALSE; + boolean_t is_if = (ifname != NULL); + + pdtbl = i_ipadm_get_propdesc_table(proto); + + /* + * We already checked for supported protocol, + * pdtbl better not be NULL. + */ + assert(pdtbl != NULL); + + for (pdp = pdtbl; pdp->ipd_name != NULL; pdp++) { + if (strcmp(pname, pdp->ipd_name) == 0) { + matched_name = B_TRUE; + if (proto == pdp->ipd_proto) + break; + } + } + + if (pdp->ipd_name != NULL) { + /* + * check whether the property can be + * applied on an interface + */ + if (is_if && !(pdp->ipd_class & IPADMPROP_CLASS_IF)) + return (IPADM_INVALID_ARG); + /* + * check whether the property can be + * applied on a module + */ + if (!is_if && !(pdp->ipd_class & IPADMPROP_CLASS_MODULE)) + return (IPADM_INVALID_ARG); + + } else { + /* + * if we matched name, but failed protocol check, + * then return error + */ + if (matched_name) + return (IPADM_INVALID_ARG); + + /* there are no private interface properties */ + if (is_if) + return (IPADM_PROP_UNKNOWN); + + /* private protocol properties, pass it to kernel directly */ + pdp = &ipadm_privprop; + (void) strlcpy(priv_propname, pname, sizeof (priv_propname)); + pdp->ipd_name = priv_propname; + } + + switch (valtype) { + case IPADM_OPT_PERM: + status = pdp->ipd_get(iph, ifname, pdp, buf, bufsize, proto, + MOD_PROP_PERM); + if (status == IPADM_SUCCESS) + i_ipadm_perm2str(buf, bufsize); + break; + case IPADM_OPT_ACTIVE: + status = pdp->ipd_get(iph, ifname, pdp, buf, bufsize, proto, + MOD_PROP_ACTIVE); + break; + case IPADM_OPT_DEFAULT: + status = pdp->ipd_get(iph, ifname, pdp, buf, bufsize, proto, + MOD_PROP_DEFAULT); + break; + case IPADM_OPT_POSSIBLE: + if (pdp->ipd_get_range != NULL) { + status = pdp->ipd_get_range(iph, ifname, pdp, buf, + bufsize, proto, MOD_PROP_POSSIBLE); + break; + } + buf[0] = '\0'; + break; + case IPADM_OPT_PERSIST: + /* retrieve from database */ + if (is_if) + status = i_ipadm_get_persist_propval(iph, pdp, buf, + bufsize, ifname); + else + status = i_ipadm_get_persist_propval(iph, pdp, buf, + bufsize, ipadm_proto2str(proto)); + break; + default: + status = IPADM_INVALID_ARG; + break; + } + return (status); +} + +/* + * Get protocol property of the specified protocol. + */ +ipadm_status_t +ipadm_get_prop(ipadm_handle_t iph, const char *pname, char *buf, + uint_t *bufsize, uint_t proto, uint_t valtype) +{ + /* + * validate the arguments of the function. + */ + if (iph == NULL || pname == NULL || buf == NULL || + bufsize == NULL || *bufsize == 0) { + return (IPADM_INVALID_ARG); + } + /* + * Do we support this proto, if not return error. + */ + if (ipadm_proto2str(proto) == NULL) + return (IPADM_NOTSUP); + + return (i_ipadm_getprop_common(iph, NULL, pname, buf, bufsize, + proto, valtype)); +} + +/* + * Get interface property of the specified interface. + */ +ipadm_status_t +ipadm_get_ifprop(ipadm_handle_t iph, const char *ifname, const char *pname, + char *buf, uint_t *bufsize, uint_t proto, uint_t valtype) +{ + /* validate the arguments of the function. */ + if (iph == NULL || pname == NULL || buf == NULL || + bufsize == NULL || *bufsize == 0) { + return (IPADM_INVALID_ARG); + } + + /* Do we support this proto, if not return error. */ + if (ipadm_proto2str(proto) == NULL) + return (IPADM_NOTSUP); + + /* + * check if interface name is provided for interface property and + * is valid. + */ + if (!i_ipadm_validate_ifname(iph, ifname)) + return (IPADM_INVALID_ARG); + + return (i_ipadm_getprop_common(iph, ifname, pname, buf, bufsize, + proto, valtype)); +} + +/* + * Allocates sufficient ioctl buffers and copies property name and the + * value, among other things. If the flag IPADM_OPT_DEFAULT is set, then + * `pval' will be NULL and it instructs the kernel to reset the current + * value to property's default value. + */ +static ipadm_status_t +i_ipadm_set_prop(ipadm_handle_t iph, const void *arg, + ipadm_prop_desc_t *pdp, const void *pval, uint_t proto, uint_t flags) +{ + ipadm_status_t status = IPADM_SUCCESS; + const char *ifname = arg; + mod_ioc_prop_t *mip; + char *pname = pdp->ipd_name; + uint_t valsize, iocsize; + uint_t iocflags = 0; + + if (flags & IPADM_OPT_DEFAULT) { + iocflags |= MOD_PROP_DEFAULT; + } else if (flags & IPADM_OPT_ACTIVE) { + iocflags |= MOD_PROP_ACTIVE; + if (flags & IPADM_OPT_APPEND) + iocflags |= MOD_PROP_APPEND; + else if (flags & IPADM_OPT_REMOVE) + iocflags |= MOD_PROP_REMOVE; + } + + if (pval != NULL) { + valsize = strlen(pval); + iocsize = sizeof (mod_ioc_prop_t) + valsize - 1; + } else { + valsize = 0; + iocsize = sizeof (mod_ioc_prop_t); + } + + if ((mip = calloc(1, iocsize)) == NULL) + return (IPADM_NO_BUFS); + + mip->mpr_version = MOD_PROP_VERSION; + mip->mpr_flags = iocflags; + mip->mpr_proto = proto; + if (ifname != NULL) { + (void) strlcpy(mip->mpr_ifname, ifname, + sizeof (mip->mpr_ifname)); + } + + (void) strlcpy(mip->mpr_name, pname, sizeof (mip->mpr_name)); + mip->mpr_valsize = valsize; + if (pval != NULL) + bcopy(pval, mip->mpr_val, valsize); + + if (i_ipadm_strioctl(iph->iph_sock, SIOCSETPROP, (char *)mip, + iocsize) < 0) { + if (errno == ENOENT) + status = IPADM_PROP_UNKNOWN; + else + status = ipadm_errno2status(errno); + } + free(mip); + return (status); +} + +/* + * Common function for modifying both protocol/interface property. + * + * If: + * IPADM_OPT_PERSIST is set then the value is persisted. + * IPADM_OPT_DEFAULT is set then the default value for the property will + * be applied. + */ +static ipadm_status_t +i_ipadm_setprop_common(ipadm_handle_t iph, const char *ifname, + const char *pname, const char *buf, uint_t proto, uint_t pflags) +{ + ipadm_status_t status = IPADM_SUCCESS; + boolean_t persist = (pflags & IPADM_OPT_PERSIST); + boolean_t reset = (pflags & IPADM_OPT_DEFAULT); + ipadm_prop_desc_t *pdp, *pdtbl; + boolean_t is_if = (ifname != NULL); + char priv_propname[MAXPROPNAMELEN]; + boolean_t matched_name = B_FALSE; + + /* Check that property value is within the allowed size */ + if (!reset && strnlen(buf, MAXPROPVALLEN) >= MAXPROPVALLEN) + return (IPADM_INVALID_ARG); + + pdtbl = i_ipadm_get_propdesc_table(proto); + /* + * We already checked for supported protocol, + * pdtbl better not be NULL. + */ + assert(pdtbl != NULL); + + /* Walk through the property table to match the given property name */ + for (pdp = pdtbl; pdp->ipd_name != NULL; pdp++) { + /* + * we find the entry which matches <pname, proto> tuple + */ + if (strcmp(pname, pdp->ipd_name) == 0) { + matched_name = B_TRUE; + if (pdp->ipd_proto == proto) + break; + } + } + + if (pdp->ipd_name != NULL) { + /* do some sanity checks */ + if (is_if) { + if (!(pdp->ipd_class & IPADMPROP_CLASS_IF)) + return (IPADM_INVALID_ARG); + } else { + if (!(pdp->ipd_class & IPADMPROP_CLASS_MODULE)) + return (IPADM_INVALID_ARG); + } + } else { + /* + * if we matched name, but failed protocol check, + * then return error. + */ + if (matched_name) + return (IPADM_BAD_PROTOCOL); + + /* Possibly a private property, pass it to kernel directly */ + + /* there are no private interface properties */ + if (is_if) + return (IPADM_PROP_UNKNOWN); + + pdp = &ipadm_privprop; + (void) strlcpy(priv_propname, pname, sizeof (priv_propname)); + pdp->ipd_name = priv_propname; + } + + status = pdp->ipd_set(iph, ifname, pdp, buf, proto, pflags); + if (status != IPADM_SUCCESS) + return (status); + + if (persist) { + if (is_if) + status = i_ipadm_persist_propval(iph, pdp, buf, ifname, + pflags); + else + status = i_ipadm_persist_propval(iph, pdp, buf, + ipadm_proto2str(proto), pflags); + } + return (status); +} + +/* + * Sets the property value of the specified interface + */ +ipadm_status_t +ipadm_set_ifprop(ipadm_handle_t iph, const char *ifname, const char *pname, + const char *buf, uint_t proto, uint_t pflags) +{ + boolean_t reset = (pflags & IPADM_OPT_DEFAULT); + ipadm_status_t status; + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + /* + * validate the arguments of the function. + */ + if (iph == NULL || pname == NULL || (!reset && buf == NULL) || + pflags == 0 || pflags == IPADM_OPT_PERSIST || + (pflags & ~(IPADM_COMMON_OPT_MASK|IPADM_OPT_DEFAULT))) { + return (IPADM_INVALID_ARG); + } + + /* + * Do we support this protocol, if not return error. + */ + if (ipadm_proto2str(proto) == NULL) + return (IPADM_NOTSUP); + + /* + * Validate the interface and check if a persistent + * operation is performed on a temporary object. + */ + status = i_ipadm_validate_if(iph, ifname, proto, pflags); + if (status != IPADM_SUCCESS) + return (status); + + return (i_ipadm_setprop_common(iph, ifname, pname, buf, proto, + pflags)); +} + +/* + * Sets the property value of the specified protocol. + */ +ipadm_status_t +ipadm_set_prop(ipadm_handle_t iph, const char *pname, const char *buf, + uint_t proto, uint_t pflags) +{ + boolean_t reset = (pflags & IPADM_OPT_DEFAULT); + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + /* + * validate the arguments of the function. + */ + if (iph == NULL || pname == NULL ||(!reset && buf == NULL) || + pflags == 0 || pflags == IPADM_OPT_PERSIST || + (pflags & ~(IPADM_COMMON_OPT_MASK|IPADM_OPT_DEFAULT| + IPADM_OPT_APPEND|IPADM_OPT_REMOVE))) { + return (IPADM_INVALID_ARG); + } + + /* + * Do we support this proto, if not return error. + */ + if (ipadm_proto2str(proto) == NULL) + return (IPADM_NOTSUP); + + return (i_ipadm_setprop_common(iph, NULL, pname, buf, proto, + pflags)); +} + +/* helper function for ipadm_walk_proptbl */ +static void +i_ipadm_walk_proptbl(ipadm_prop_desc_t *pdtbl, uint_t proto, uint_t class, + ipadm_prop_wfunc_t *func, void *arg) +{ + ipadm_prop_desc_t *pdp; + + for (pdp = pdtbl; pdp->ipd_name != NULL; pdp++) { + if (!(pdp->ipd_class & class)) + continue; + + if (proto != MOD_PROTO_NONE && !(pdp->ipd_proto & proto)) + continue; + + /* + * we found a class specific match, call the + * user callback function. + */ + if (func(arg, pdp->ipd_name, pdp->ipd_proto) == B_FALSE) + break; + } +} + +/* + * Walks through all the properties, for a given protocol and property class + * (protocol or interface). + * + * Further if proto == MOD_PROTO_NONE, then it walks through all the supported + * protocol property tables. + */ +ipadm_status_t +ipadm_walk_proptbl(uint_t proto, uint_t class, ipadm_prop_wfunc_t *func, + void *arg) +{ + ipadm_prop_desc_t *pdtbl; + ipadm_status_t status = IPADM_SUCCESS; + int i; + int count = A_CNT(protocols); + + if (func == NULL) + return (IPADM_INVALID_ARG); + + switch (class) { + case IPADMPROP_CLASS_ADDR: + pdtbl = ipadm_addrprop_table; + break; + case IPADMPROP_CLASS_IF: + case IPADMPROP_CLASS_MODULE: + pdtbl = i_ipadm_get_propdesc_table(proto); + if (pdtbl == NULL && proto != MOD_PROTO_NONE) + return (IPADM_INVALID_ARG); + break; + default: + return (IPADM_INVALID_ARG); + } + + if (pdtbl != NULL) { + /* + * proto will be MOD_PROTO_NONE in the case of + * IPADMPROP_CLASS_ADDR. + */ + i_ipadm_walk_proptbl(pdtbl, proto, class, func, arg); + } else { + /* Walk thru all the protocol tables, we support */ + for (i = 0; i < count; i++) { + pdtbl = i_ipadm_get_propdesc_table(protocols[i]); + i_ipadm_walk_proptbl(pdtbl, protocols[i], class, func, + arg); + } + } + return (status); +} + +/* + * Given a property name, walks through all the instances of a property name. + * Some properties have two instances one for v4 interfaces and another for v6 + * interfaces. For example: MTU. MTU can have different values for v4 and v6. + * Therefore there are two properties for 'MTU'. + * + * This function invokes `func' for every instance of property `pname' + */ +ipadm_status_t +ipadm_walk_prop(const char *pname, uint_t proto, uint_t class, + ipadm_prop_wfunc_t *func, void *arg) +{ + ipadm_prop_desc_t *pdtbl, *pdp; + ipadm_status_t status = IPADM_SUCCESS; + boolean_t matched = B_FALSE; + + if (pname == NULL || func == NULL) + return (IPADM_INVALID_ARG); + + switch (class) { + case IPADMPROP_CLASS_ADDR: + pdtbl = ipadm_addrprop_table; + break; + case IPADMPROP_CLASS_IF: + case IPADMPROP_CLASS_MODULE: + pdtbl = i_ipadm_get_propdesc_table(proto); + break; + default: + return (IPADM_INVALID_ARG); + } + + if (pdtbl == NULL) + return (IPADM_INVALID_ARG); + + for (pdp = pdtbl; pdp->ipd_name != NULL; pdp++) { + if (strcmp(pname, pdp->ipd_name) != 0) + continue; + if (!(pdp->ipd_proto & proto)) + continue; + matched = B_TRUE; + /* we found a match, call the callback function */ + if (func(arg, pdp->ipd_name, pdp->ipd_proto) == B_FALSE) + break; + } + if (!matched) + status = IPADM_PROP_UNKNOWN; + return (status); +} + +/* ARGSUSED */ +ipadm_status_t +i_ipadm_get_onoff(ipadm_handle_t iph, const void *arg, ipadm_prop_desc_t *dp, + char *buf, uint_t *bufsize, uint_t proto, uint_t valtype) +{ + (void) snprintf(buf, *bufsize, "%s,%s", IPADM_ONSTR, IPADM_OFFSTR); + return (IPADM_SUCCESS); +} + +/* + * Makes a door call to ipmgmtd to retrieve the persisted property value + */ +ipadm_status_t +i_ipadm_get_persist_propval(ipadm_handle_t iph, ipadm_prop_desc_t *pdp, + char *gbuf, uint_t *gbufsize, const void *object) +{ + ipmgmt_prop_arg_t parg; + ipmgmt_getprop_rval_t rval, *rvalp; + size_t nbytes; + int err = 0; + + bzero(&parg, sizeof (parg)); + parg.ia_cmd = IPMGMT_CMD_GETPROP; + i_ipadm_populate_proparg(&parg, pdp, NULL, object); + + rvalp = &rval; + err = ipadm_door_call(iph, &parg, sizeof (parg), (void **)&rvalp, + sizeof (rval), B_FALSE); + if (err == 0) { + /* assert that rvalp was not reallocated */ + assert(rvalp == &rval); + + /* `ir_pval' contains the property value */ + nbytes = snprintf(gbuf, *gbufsize, "%s", rvalp->ir_pval); + if (nbytes >= *gbufsize) { + /* insufficient buffer space */ + *gbufsize = nbytes + 1; + err = ENOBUFS; + } + } + return (ipadm_errno2status(err)); +} + +/* + * Persists the property value for a given property in the data store + */ +ipadm_status_t +i_ipadm_persist_propval(ipadm_handle_t iph, ipadm_prop_desc_t *pdp, + const char *pval, const void *object, uint_t flags) +{ + ipmgmt_prop_arg_t parg; + int err = 0; + + bzero(&parg, sizeof (parg)); + i_ipadm_populate_proparg(&parg, pdp, pval, object); + + /* + * Check if value to be persisted need to be appended or removed. This + * is required for multi-valued property. + */ + if (flags & IPADM_OPT_APPEND) + parg.ia_flags |= IPMGMT_APPEND; + if (flags & IPADM_OPT_REMOVE) + parg.ia_flags |= IPMGMT_REMOVE; + + if (flags & (IPADM_OPT_DEFAULT|IPADM_OPT_REMOVE)) + parg.ia_cmd = IPMGMT_CMD_RESETPROP; + else + parg.ia_cmd = IPMGMT_CMD_SETPROP; + + err = ipadm_door_call(iph, &parg, sizeof (parg), NULL, 0, B_FALSE); + + /* + * its fine if there were no entry in the DB to delete. The user + * might be changing property value, which was not changed + * persistently. + */ + if (err == ENOENT) + err = 0; + return (ipadm_errno2status(err)); +} + +/* + * Called during boot. + * + * Walk through the DB and apply all the global module properties. We plow + * through the DB even if we fail to apply property. + */ +/* ARGSUSED */ +boolean_t +ipadm_db_init(void *cbarg, nvlist_t *db_nvl, char *buf, size_t buflen, + int *errp) +{ + ipadm_handle_t iph = cbarg; + nvpair_t *nvp, *pnvp; + char *strval = NULL, *name, *mod = NULL; + uint_t proto; + + /* + * We could have used nvl_exists() directly, however we need several + * calls to it and each call traverses the list. Since this codepath + * is exercised during boot, let's traverse the list ourselves and do + * the necessary checks. + */ + for (nvp = nvlist_next_nvpair(db_nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(db_nvl, nvp)) { + name = nvpair_name(nvp); + if (IPADM_PRIV_NVP(name)) { + if (strcmp(name, IPADM_NVP_IFNAME) == 0 || + strcmp(name, IPADM_NVP_AOBJNAME) == 0) + return (B_TRUE); + else if (strcmp(name, IPADM_NVP_PROTONAME) == 0 && + nvpair_value_string(nvp, &mod) != 0) + return (B_TRUE); + } else { + /* possible a property */ + pnvp = nvp; + } + } + + /* if we are here than we found a global property */ + assert(mod != NULL); + assert(nvpair_type(pnvp) == DATA_TYPE_STRING); + + proto = ipadm_str2proto(mod); + if (nvpair_value_string(pnvp, &strval) == 0) { + (void) ipadm_set_prop(iph, name, strval, proto, + IPADM_OPT_ACTIVE); + } + + return (B_TRUE); +} + +/* initialize global module properties */ +ipadm_status_t +ipadm_init_prop() +{ + ipadm_handle_t iph = NULL; + ipadm_status_t status; + int err; + + /* check for solaris.network.interface.config authorization */ + if (!ipadm_check_auth()) + return (IPADM_EAUTH); + + if ((status = ipadm_open(&iph, IPH_INIT)) != IPADM_SUCCESS) + return (status); + + err = ipadm_rw_db(ipadm_db_init, iph, IPADM_DB_FILE, IPADM_FILE_MODE, + IPADM_DB_READ); + + ipadm_close(iph); + return (ipadm_errno2status(err)); +} + +/* + * This is called from ipadm_set_ifprop() to validate the set operation. + * It does the following steps: + * 1. Validates the interface name. + * 2. Fails if it is an IPMP meta-interface or an underlying interface. + * 3. In case of a persistent operation, verifies that the + * interface is persistent. + */ +static ipadm_status_t +i_ipadm_validate_if(ipadm_handle_t iph, const char *ifname, + uint_t proto, uint_t flags) +{ + sa_family_t af, other_af; + ipadm_status_t status; + boolean_t p_exists; + boolean_t af_exists, other_af_exists, a_exists; + + /* Check if the interface name is valid. */ + if (!i_ipadm_validate_ifname(iph, ifname)) + return (IPADM_INVALID_ARG); + + af = (proto == MOD_PROTO_IPV6 ? AF_INET6 : AF_INET); + /* + * Setting properties on an IPMP meta-interface or underlying + * interface is not supported. + */ + if (i_ipadm_is_ipmp(iph, ifname) || i_ipadm_is_under_ipmp(iph, ifname)) + return (IPADM_NOTSUP); + + /* Check if interface exists in the persistent configuration. */ + status = i_ipadm_if_pexists(iph, ifname, af, &p_exists); + if (status != IPADM_SUCCESS) + return (status); + + /* Check if interface exists in the active configuration. */ + af_exists = ipadm_if_enabled(iph, ifname, af); + other_af = (af == AF_INET ? AF_INET6 : AF_INET); + other_af_exists = ipadm_if_enabled(iph, ifname, other_af); + a_exists = (af_exists || other_af_exists); + if (!a_exists && p_exists) + return (IPADM_OP_DISABLE_OBJ); + if (!af_exists) + return (IPADM_ENXIO); + + /* + * If a persistent operation is requested, check if the underlying + * IP interface is persistent. + */ + if ((flags & IPADM_OPT_PERSIST) && !p_exists) + return (IPADM_TEMPORARY_OBJ); + return (IPADM_SUCCESS); +} diff --git a/usr/src/lib/libipadm/common/libipadm.c b/usr/src/lib/libipadm/common/libipadm.c new file mode 100644 index 0000000000..2c18331a7b --- /dev/null +++ b/usr/src/lib/libipadm/common/libipadm.c @@ -0,0 +1,922 @@ +/* + * 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 <fcntl.h> +#include <unistd.h> +#include <stropts.h> +#include <sys/sockio.h> +#include <sys/types.h> +#include <sys/stat.h> +#include <sys/socket.h> +#include <net/route.h> +#include <netinet/in.h> +#include <inet/ip.h> +#include <arpa/inet.h> +#include <libintl.h> +#include <libdlpi.h> +#include <libinetutil.h> +#include <libdladm.h> +#include <libdllink.h> +#include <libdliptun.h> +#include <strings.h> +#include <zone.h> +#include <ctype.h> +#include <limits.h> +#include <assert.h> +#include <netdb.h> +#include <pwd.h> +#include <auth_attr.h> +#include <secdb.h> +#include <nss_dbdefs.h> +#include "libipadm_impl.h" + +/* error codes and text description */ +static struct ipadm_error_info { + ipadm_status_t error_code; + const char *error_desc; +} ipadm_errors[] = { + { IPADM_SUCCESS, "Operation succeeded" }, + { IPADM_FAILURE, "Operation failed" }, + { IPADM_EAUTH, "Insufficient user authorizations" }, + { IPADM_EPERM, "Permission denied" }, + { IPADM_NO_BUFS, "No buffer space available" }, + { IPADM_NO_MEMORY, "Insufficient memory" }, + { IPADM_BAD_ADDR, "Invalid address" }, + { IPADM_BAD_PROTOCOL, "Incorrect protocol family for operation" }, + { IPADM_DAD_FOUND, "Duplicate address detected" }, + { IPADM_EXISTS, "Already exists" }, + { IPADM_IF_EXISTS, "Interface already exists" }, + { IPADM_ADDROBJ_EXISTS, "Address object already exists" }, + { IPADM_ADDRCONF_EXISTS, "Addrconf already in progress" }, + { IPADM_ENXIO, "Interface does not exist" }, + { IPADM_GRP_NOTEMPTY, "IPMP group is not empty" }, + { IPADM_INVALID_ARG, "Invalid argument provided" }, + { IPADM_INVALID_NAME, "Invalid name" }, + { IPADM_DLPI_FAILURE, "Could not open DLPI link" }, + { IPADM_DLADM_FAILURE, "Datalink does not exist" }, + { IPADM_PROP_UNKNOWN, "Unknown property" }, + { IPADM_ERANGE, "Value is outside the allowed range" }, + { IPADM_ESRCH, "Value does not exist" }, + { IPADM_EOVERFLOW, "Number of values exceeds the allowed limit" }, + { IPADM_NOTFOUND, "Object not found" }, + { IPADM_IF_INUSE, "Interface already in use" }, + { IPADM_ADDR_INUSE, "Address already in use" }, + { IPADM_BAD_HOSTNAME, "Hostname maps to multiple IP addresses" }, + { IPADM_ADDR_NOTAVAIL, "Can't assign requested address" }, + { IPADM_ALL_ADDRS_NOT_ENABLED, "All addresses could not be enabled" }, + { IPADM_NDPD_NOT_RUNNING, "IPv6 autoconf daemon in.ndpd not running" }, + { IPADM_DHCP_START_ERROR, "Could not start dhcpagent" }, + { IPADM_DHCP_IPC_ERROR, "Could not communicate with dhcpagent" }, + { IPADM_DHCP_IPC_TIMEOUT, "Communication with dhcpagent timed out" }, + { IPADM_TEMPORARY_OBJ, "Persistent operation on temporary object" }, + { IPADM_IPC_ERROR, "Could not communicate with ipmgmtd" }, + { IPADM_NOTSUP, "Operation not supported" }, + { IPADM_OP_DISABLE_OBJ, "Operation not supported on disabled object" }, + { IPADM_EBADE, "Invalid data exchange with daemon" } +}; + +#define IPADM_NUM_ERRORS (sizeof (ipadm_errors) / sizeof (*ipadm_errors)) + +ipadm_status_t +ipadm_errno2status(int error) +{ + switch (error) { + case 0: + return (IPADM_SUCCESS); + case ENXIO: + return (IPADM_ENXIO); + case ENOMEM: + return (IPADM_NO_MEMORY); + case ENOBUFS: + return (IPADM_NO_BUFS); + case EINVAL: + return (IPADM_INVALID_ARG); + case EBUSY: + return (IPADM_IF_INUSE); + case EEXIST: + return (IPADM_EXISTS); + case EADDRNOTAVAIL: + return (IPADM_ADDR_NOTAVAIL); + case EADDRINUSE: + return (IPADM_ADDR_INUSE); + case ENOENT: + return (IPADM_NOTFOUND); + case ERANGE: + return (IPADM_ERANGE); + case EPERM: + return (IPADM_EPERM); + case ENOTSUP: + case EOPNOTSUPP: + return (IPADM_NOTSUP); + case EBADF: + return (IPADM_IPC_ERROR); + case EBADE: + return (IPADM_EBADE); + case ESRCH: + return (IPADM_ESRCH); + case EOVERFLOW: + return (IPADM_EOVERFLOW); + default: + return (IPADM_FAILURE); + } +} + +/* + * Returns a message string for the given libipadm error status. + */ +const char * +ipadm_status2str(ipadm_status_t status) +{ + int i; + + for (i = 0; i < IPADM_NUM_ERRORS; i++) { + if (status == ipadm_errors[i].error_code) + return (dgettext(TEXT_DOMAIN, + ipadm_errors[i].error_desc)); + } + + return (dgettext(TEXT_DOMAIN, "<unknown error>")); +} + +/* + * Opens a handle to libipadm. + * Possible values for flags: + * IPH_VRRP: Used by VRRP daemon to set the socket option SO_VRRP. + * IPH_LEGACY: This is used whenever an application needs to provide a + * logical interface name while creating or deleting + * interfaces and static addresses. + * IPH_INIT: Used by ipadm_init_prop(), to initialize protocol properties + * on reboot. + */ +ipadm_status_t +ipadm_open(ipadm_handle_t *handle, uint32_t flags) +{ + ipadm_handle_t iph; + ipadm_status_t status = IPADM_SUCCESS; + zoneid_t zoneid; + ushort_t zflags; + int on = B_TRUE; + + if (handle == NULL) + return (IPADM_INVALID_ARG); + *handle = NULL; + + if (flags & ~(IPH_VRRP|IPH_LEGACY|IPH_INIT)) + return (IPADM_INVALID_ARG); + + if ((iph = calloc(1, sizeof (struct ipadm_handle))) == NULL) + return (IPADM_NO_MEMORY); + iph->iph_sock = -1; + iph->iph_sock6 = -1; + iph->iph_door_fd = -1; + iph->iph_flags = flags; + (void) pthread_mutex_init(&iph->iph_lock, NULL); + + if ((iph->iph_sock = socket(AF_INET, SOCK_DGRAM, 0)) < 0 || + (iph->iph_sock6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { + goto errnofail; + } + + /* + * We open a handle to libdladm here, to facilitate some daemons (like + * nwamd) which opens handle to libipadm before devfsadmd installs the + * right device permissions into the kernel and requires "all" + * privileges to open DLD_CONTROL_DEV. + * + * In a non-global shared-ip zone there will be no DLD_CONTROL_DEV node + * and dladm_open() will fail. So, we avoid this by not calling + * dladm_open() for such zones. + */ + zoneid = getzoneid(); + if (zoneid != GLOBAL_ZONEID) { + if (zone_getattr(zoneid, ZONE_ATTR_FLAGS, &zflags, + sizeof (zflags)) < 0) { + goto errnofail; + } + } + if ((zoneid == GLOBAL_ZONEID) || (zflags & ZF_NET_EXCL)) { + if (dladm_open(&iph->iph_dlh) != DLADM_STATUS_OK) { + ipadm_close(iph); + return (IPADM_DLADM_FAILURE); + } + } else { + assert(zoneid != GLOBAL_ZONEID); + iph->iph_dlh = NULL; + } + if (flags & IPH_VRRP) { + if (setsockopt(iph->iph_sock6, SOL_SOCKET, SO_VRRP, &on, + sizeof (on)) < 0 || setsockopt(iph->iph_sock, SOL_SOCKET, + SO_VRRP, &on, sizeof (on)) < 0) { + goto errnofail; + } + } + *handle = iph; + return (status); + +errnofail: + status = ipadm_errno2status(errno); + ipadm_close(iph); + return (status); +} + +/* + * Closes and frees the libipadm handle. + */ +void +ipadm_close(ipadm_handle_t iph) +{ + if (iph == NULL) + return; + if (iph->iph_sock != -1) + (void) close(iph->iph_sock); + if (iph->iph_sock6 != -1) + (void) close(iph->iph_sock6); + if (iph->iph_door_fd != -1) + (void) close(iph->iph_door_fd); + dladm_close(iph->iph_dlh); + (void) pthread_mutex_destroy(&iph->iph_lock); + free(iph); +} + +/* + * Checks if the caller has the authorization to configure network + * interfaces. + */ +boolean_t +ipadm_check_auth(void) +{ + struct passwd pwd; + char buf[NSS_BUFLEN_PASSWD]; + + /* get the password entry for the given user ID */ + if (getpwuid_r(getuid(), &pwd, buf, sizeof (buf)) == NULL) + return (B_FALSE); + + /* check for presence of given authorization */ + return (chkauthattr(NETWORK_INTERFACE_CONFIG_AUTH, pwd.pw_name) != 0); +} + +/* + * Stores the index value of the interface in `ifname' for the address + * family `af' into the buffer pointed to by `index'. + */ +static ipadm_status_t +i_ipadm_get_index(ipadm_handle_t iph, const char *ifname, sa_family_t af, + int *index) +{ + struct lifreq lifr; + int sock; + + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (af == AF_INET) + sock = iph->iph_sock; + else + sock = iph->iph_sock6; + + if (ioctl(sock, SIOCGLIFINDEX, (caddr_t)&lifr) < 0) + return (ipadm_errno2status(errno)); + *index = lifr.lifr_index; + + return (IPADM_SUCCESS); +} + +/* + * Maximum amount of time (in milliseconds) to wait for Duplicate Address + * Detection to complete in the kernel. + */ +#define DAD_WAIT_TIME 1000 + +/* + * Any time that flags are changed on an interface where either the new or the + * existing flags have IFF_UP set, we'll get a RTM_NEWADDR message to + * announce the new address added and its flag status. + * We wait here for that message and look for IFF_UP. + * If something's amiss with the kernel, though, we don't wait forever. + * (Note that IFF_DUPLICATE is a high-order bit, and we cannot see + * it in the routing socket messages.) + */ +static ipadm_status_t +i_ipadm_dad_wait(ipadm_handle_t handle, const char *lifname, sa_family_t af, + int rtsock) +{ + struct pollfd fds[1]; + union { + struct if_msghdr ifm; + char buf[1024]; + } msg; + int index; + ipadm_status_t retv; + uint64_t flags; + hrtime_t starttime, now; + + fds[0].fd = rtsock; + fds[0].events = POLLIN; + fds[0].revents = 0; + + retv = i_ipadm_get_index(handle, lifname, af, &index); + if (retv != IPADM_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_NEWADDR) + continue; + /* Note that ifm_index is just 16 bits */ + if (index == msg.ifm.ifm_index && (msg.ifm.ifm_flags & IFF_UP)) + return (IPADM_SUCCESS); + } + + retv = i_ipadm_get_flags(handle, lifname, af, &flags); + if (retv != IPADM_SUCCESS) + return (retv); + if (flags & IFF_DUPLICATE) + return (IPADM_DAD_FOUND); + + return (IPADM_SUCCESS); +} + +/* + * Sets the flags `on_flags' and resets the flags `off_flags' for the logical + * interface in `lifname'. + * + * 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. + */ +ipadm_status_t +i_ipadm_set_flags(ipadm_handle_t iph, const char *lifname, sa_family_t af, + uint64_t on_flags, uint64_t off_flags) +{ + struct lifreq lifr; + uint64_t oflags; + ipadm_status_t ret; + int rtsock = -1; + int sock, err; + + ret = i_ipadm_get_flags(iph, lifname, af, &oflags); + if (ret != IPADM_SUCCESS) + return (ret); + + sock = (af == AF_INET ? iph->iph_sock : iph->iph_sock6); + + /* + * Any time flags are changed on an interface that has IFF_UP set, + * we get a routing socket message. We care about the status, + * though, only when the new flags are marked "up." + */ + if (!(oflags & IFF_UP) && (on_flags & IFF_UP)) + rtsock = socket(PF_ROUTE, SOCK_RAW, af); + + oflags |= on_flags; + oflags &= ~off_flags; + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lifname, sizeof (lifr.lifr_name)); + lifr.lifr_flags = oflags; + if (ioctl(sock, SIOCSLIFFLAGS, (caddr_t)&lifr) < 0) { + err = errno; + if (rtsock != -1) + (void) close(rtsock); + return (ipadm_errno2status(err)); + } + if (rtsock == -1) { + return (IPADM_SUCCESS); + } else { + /* Wait for DAD to complete. */ + ret = i_ipadm_dad_wait(iph, lifname, af, rtsock); + (void) close(rtsock); + return (ret); + } +} + +/* + * Returns the flags value for the logical interface in `lifname' + * in the buffer pointed to by `flags'. + */ +ipadm_status_t +i_ipadm_get_flags(ipadm_handle_t iph, const char *lifname, sa_family_t af, + uint64_t *flags) +{ + struct lifreq lifr; + int sock; + + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, lifname, sizeof (lifr.lifr_name)); + if (af == AF_INET) + sock = iph->iph_sock; + else + sock = iph->iph_sock6; + + if (ioctl(sock, SIOCGLIFFLAGS, (caddr_t)&lifr) < 0) { + return (ipadm_errno2status(errno)); + } + *flags = lifr.lifr_flags; + + return (IPADM_SUCCESS); +} + +/* + * Determines whether or not an interface name represents a loopback + * interface, before the interface has been plumbed. + * It is assumed that the interface name in `ifname' is of correct format + * as verified by ifparse_ifspec(). + * + * Returns: B_TRUE if loopback, B_FALSE if not. + */ +boolean_t +i_ipadm_is_loopback(const char *ifname) +{ + int len = strlen(LOOPBACK_IF); + + return (strncmp(ifname, LOOPBACK_IF, len) == 0 && + (ifname[len] == '\0' || ifname[len] == IPADM_LOGICAL_SEP)); +} + +/* + * Determines whether or not an interface name represents a vni + * interface, before the interface has been plumbed. + * It is assumed that the interface name in `ifname' is of correct format + * as verified by ifparse_ifspec(). + * + * Returns: B_TRUE if vni, B_FALSE if not. + */ +boolean_t +i_ipadm_is_vni(const char *ifname) +{ + ifspec_t ifsp; + + return (ifparse_ifspec(ifname, &ifsp) && + strcmp(ifsp.ifsp_devnm, "vni") == 0); +} + +/* + * Returns B_TRUE if `ifname' is an IP interface on a 6to4 tunnel. + */ +boolean_t +i_ipadm_is_6to4(ipadm_handle_t iph, char *ifname) +{ + dladm_status_t dlstatus; + datalink_class_t class; + iptun_params_t params; + datalink_id_t linkid; + + if (iph->iph_dlh == NULL) { + assert(getzoneid() != GLOBAL_ZONEID); + return (B_FALSE); + } + dlstatus = dladm_name2info(iph->iph_dlh, ifname, &linkid, NULL, + &class, NULL); + if (dlstatus == DLADM_STATUS_OK && class == DATALINK_CLASS_IPTUN) { + params.iptun_param_linkid = linkid; + dlstatus = dladm_iptun_getparams(iph->iph_dlh, ¶ms, + DLADM_OPT_ACTIVE); + if (dlstatus == DLADM_STATUS_OK && + params.iptun_param_type == IPTUN_TYPE_6TO4) { + return (B_TRUE); + } + } + return (B_FALSE); +} + +/* + * Returns B_TRUE if `ifname' represents an IPMP underlying interface. + */ +boolean_t +i_ipadm_is_under_ipmp(ipadm_handle_t iph, const char *ifname) +{ + struct lifreq lifr; + + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + if (ioctl(iph->iph_sock, SIOCGLIFGROUPNAME, (caddr_t)&lifr) < 0) { + if (ioctl(iph->iph_sock6, SIOCGLIFGROUPNAME, + (caddr_t)&lifr) < 0) { + return (B_FALSE); + } + } + return (lifr.lifr_groupname[0] != '\0'); +} + +/* + * Returns B_TRUE if `ifname' represents an IPMP meta-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); +} + +/* + * For a given interface name, ipadm_if_enabled() checks if v4 + * or v6 or both IP interfaces exist in the active configuration. + */ +boolean_t +ipadm_if_enabled(ipadm_handle_t iph, const char *ifname, sa_family_t af) +{ + struct lifreq lifr; + int s4 = iph->iph_sock; + int s6 = iph->iph_sock6; + + bzero(&lifr, sizeof (lifr)); + (void) strlcpy(lifr.lifr_name, ifname, sizeof (lifr.lifr_name)); + switch (af) { + case AF_INET: + if (ioctl(s4, SIOCGLIFFLAGS, (caddr_t)&lifr) == 0) + return (B_TRUE); + break; + case AF_INET6: + if (ioctl(s6, SIOCGLIFFLAGS, (caddr_t)&lifr) == 0) + return (B_TRUE); + break; + case AF_UNSPEC: + if (ioctl(s4, SIOCGLIFFLAGS, (caddr_t)&lifr) == 0 || + ioctl(s6, SIOCGLIFFLAGS, (caddr_t)&lifr) == 0) { + return (B_TRUE); + } + } + return (B_FALSE); +} + +/* + * Apply the interface property by retrieving information from nvl. + */ +static ipadm_status_t +i_ipadm_init_ifprop(ipadm_handle_t iph, nvlist_t *nvl) +{ + nvpair_t *nvp; + char *name, *pname = NULL; + char *protostr = NULL, *ifname = NULL, *pval = NULL; + uint_t proto; + int err = 0; + + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_IFNAME) == 0) { + if ((err = nvpair_value_string(nvp, &ifname)) != 0) + break; + } else if (strcmp(name, IPADM_NVP_PROTONAME) == 0) { + if ((err = nvpair_value_string(nvp, &protostr)) != 0) + break; + } else { + assert(!IPADM_PRIV_NVP(name)); + pname = name; + if ((err = nvpair_value_string(nvp, &pval)) != 0) + break; + } + } + if (err != 0) + return (ipadm_errno2status(err)); + proto = ipadm_str2proto(protostr); + return (ipadm_set_ifprop(iph, ifname, pname, pval, proto, + IPADM_OPT_ACTIVE)); +} + +/* + * Instantiate the address object or set the address object property by + * retrieving the configuration from the nvlist `nvl'. + */ +ipadm_status_t +i_ipadm_init_addrobj(ipadm_handle_t iph, nvlist_t *nvl) +{ + nvpair_t *nvp; + char *name; + char *aobjname = NULL, *pval = NULL, *ifname = NULL; + sa_family_t af = AF_UNSPEC; + ipadm_addr_type_t atype = IPADM_ADDR_NONE; + int err = 0; + ipadm_status_t status = IPADM_SUCCESS; + + for (nvp = nvlist_next_nvpair(nvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(nvl, nvp)) { + name = nvpair_name(nvp); + if (strcmp(name, IPADM_NVP_IFNAME) == 0) { + if ((err = nvpair_value_string(nvp, &ifname)) != 0) + break; + } else if (strcmp(name, IPADM_NVP_AOBJNAME) == 0) { + if ((err = nvpair_value_string(nvp, &aobjname)) != 0) + break; + } else if (i_ipadm_name2atype(name, &af, &atype)) { + break; + } else { + assert(!IPADM_PRIV_NVP(name)); + err = nvpair_value_string(nvp, &pval); + break; + } + } + if (err != 0) + return (ipadm_errno2status(err)); + + switch (atype) { + case IPADM_ADDR_STATIC: + status = i_ipadm_enable_static(iph, ifname, nvl, af); + break; + case IPADM_ADDR_DHCP: + status = i_ipadm_enable_dhcp(iph, ifname, nvl); + if (status == IPADM_DHCP_IPC_TIMEOUT) + status = IPADM_SUCCESS; + break; + case IPADM_ADDR_IPV6_ADDRCONF: + status = i_ipadm_enable_addrconf(iph, ifname, nvl); + break; + case IPADM_ADDR_NONE: + status = ipadm_set_addrprop(iph, name, pval, aobjname, + IPADM_OPT_ACTIVE); + break; + } + + return (status); +} + +/* + * Instantiate the interface object by retrieving the configuration from + * `ifnvl'. The nvlist `ifnvl' contains all the persistent configuration + * (interface properties and address objects on that interface) for the + * given `ifname'. + */ +ipadm_status_t +i_ipadm_init_ifobj(ipadm_handle_t iph, const char *ifname, nvlist_t *ifnvl) +{ + nvlist_t *nvl = NULL; + nvpair_t *nvp; + char *afstr; + ipadm_status_t status; + ipadm_status_t ret_status = IPADM_SUCCESS; + char newifname[LIFNAMSIZ]; + char *aobjstr; + + (void) strlcpy(newifname, ifname, sizeof (newifname)); + /* + * First plumb the given interface and then apply all the persistent + * interface properties and then instantiate any persistent addresses + * objects on that interface. + */ + for (nvp = nvlist_next_nvpair(ifnvl, NULL); nvp != NULL; + nvp = nvlist_next_nvpair(ifnvl, nvp)) { + if (nvpair_value_nvlist(nvp, &nvl) != 0) + continue; + + if (nvlist_lookup_string(nvl, IPADM_NVP_FAMILY, &afstr) == 0) { + status = i_ipadm_plumb_if(iph, newifname, atoi(afstr), + IPADM_OPT_ACTIVE); + /* + * If the interface is already plumbed, we should + * ignore this error because there might be address + * address objects on that interface that needs to + * be enabled again. + */ + if (status == IPADM_IF_EXISTS) + status = IPADM_SUCCESS; + } else if (nvlist_lookup_string(nvl, IPADM_NVP_AOBJNAME, + &aobjstr) == 0) { + /* + * For a static address, we need to search for + * the prefixlen in the nvlist `ifnvl'. + */ + if (nvlist_exists(nvl, IPADM_NVP_IPV4ADDR) || + nvlist_exists(nvl, IPADM_NVP_IPV6ADDR)) { + status = i_ipadm_merge_prefixlen_from_nvl(ifnvl, + nvl, aobjstr); + if (status != IPADM_SUCCESS) + continue; + } + status = i_ipadm_init_addrobj(iph, nvl); + /* + * If this address is in use on some other interface, + * we want to record an error to be returned as + * a soft error and continue processing the rest of + * the addresses. + */ + if (status == IPADM_ADDR_NOTAVAIL) { + ret_status = IPADM_ALL_ADDRS_NOT_ENABLED; + status = IPADM_SUCCESS; + } + } else { + assert(nvlist_exists(nvl, IPADM_NVP_PROTONAME)); + status = i_ipadm_init_ifprop(iph, nvl); + } + if (status != IPADM_SUCCESS) + return (status); + } + return (ret_status); +} + +/* + * Retrieves the persistent configuration for the given interface(s) in `ifs' + * by contacting the daemon and dumps the information in `allifs'. + */ +ipadm_status_t +i_ipadm_init_ifs(ipadm_handle_t iph, const char *ifs, nvlist_t **allifs) +{ + nvlist_t *nvl = NULL; + size_t nvlsize, bufsize; + ipmgmt_initif_arg_t *iargp; + char *buf = NULL, *nvlbuf = NULL; + ipmgmt_get_rval_t *rvalp = NULL; + int err; + ipadm_status_t status = IPADM_SUCCESS; + + if ((err = ipadm_str2nvlist(ifs, &nvl, IPADM_NORVAL)) != 0) + return (ipadm_errno2status(err)); + + err = nvlist_pack(nvl, &nvlbuf, &nvlsize, NV_ENCODE_NATIVE, 0); + if (err != 0) { + status = ipadm_errno2status(err); + goto done; + } + bufsize = sizeof (*iargp) + nvlsize; + if ((buf = malloc(bufsize)) == NULL) { + status = ipadm_errno2status(errno); + goto done; + } + + /* populate the door_call argument structure */ + iargp = (void *)buf; + iargp->ia_cmd = IPMGMT_CMD_INITIF; + iargp->ia_flags = 0; + iargp->ia_family = AF_UNSPEC; + iargp->ia_nvlsize = nvlsize; + (void) bcopy(nvlbuf, buf + sizeof (*iargp), nvlsize); + + if ((rvalp = malloc(sizeof (ipmgmt_get_rval_t))) == NULL) { + status = ipadm_errno2status(errno); + goto done; + } + if ((err = ipadm_door_call(iph, iargp, bufsize, (void **)&rvalp, + sizeof (*rvalp), B_TRUE)) != 0) { + status = ipadm_errno2status(err); + goto done; + } + nvlsize = rvalp->ir_nvlsize; + nvlbuf = (char *)rvalp + sizeof (ipmgmt_get_rval_t); + + /* + * nvlbuf contains a list of nvlists, each of which represents + * configuration information for the given interface(s) + */ + err = nvlist_unpack(nvlbuf, nvlsize, allifs, NV_ENCODE_NATIVE); + if (err != 0) + status = ipadm_errno2status(err); +done: + nvlist_free(nvl); + free(buf); + free(nvlbuf); + free(rvalp); + return (status); +} + +/* + * Returns B_FALSE if + * (1) `ifname' is NULL or has no string or has a string of invalid length + * (2) ifname is a logical interface and IPH_LEGACY is not set, or + */ +boolean_t +i_ipadm_validate_ifname(ipadm_handle_t iph, const char *ifname) +{ + ifspec_t ifsp; + + if (ifname == NULL || ifname[0] == '\0' || + !ifparse_ifspec(ifname, &ifsp)) + return (B_FALSE); + if (ifsp.ifsp_lunvalid) + return (ifsp.ifsp_lun > 0 && (iph->iph_flags & IPH_LEGACY)); + return (B_TRUE); +} + +/* + * Wrapper for sending a non-transparent I_STR ioctl(). + * Returns: Result from ioctl(). + */ +int +i_ipadm_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)); +} + +/* + * Make a door call to the server and checks if the door call succeeded or not. + * `is_varsize' specifies that the data returned by ipmgmtd daemon is of + * variable size and door will allocate buffer using mmap(). In such cases + * we re-allocate the required memory,n assign it to `rbufp', copy the data to + * `rbufp' and then call munmap() (see below). + * + * It also checks to see if the server side procedure ran successfully by + * checking for ir_err. Therefore, for some callers who just care about the + * return status can set `rbufp' to NULL and set `rsize' to 0. + */ +int +ipadm_door_call(ipadm_handle_t iph, void *arg, size_t asize, void **rbufp, + size_t rsize, boolean_t is_varsize) +{ + door_arg_t darg; + int err; + ipmgmt_retval_t rval, *rvalp; + + if (rbufp == NULL) { + rvalp = &rval; + rbufp = (void **)&rvalp; + rsize = sizeof (rval); + } + + darg.data_ptr = arg; + darg.data_size = asize; + darg.desc_ptr = NULL; + darg.desc_num = 0; + darg.rbuf = *rbufp; + darg.rsize = rsize; + + (void) pthread_mutex_lock(&iph->iph_lock); + /* The door descriptor is opened if it isn't already */ + if (iph->iph_door_fd == -1) { + if ((iph->iph_door_fd = open(IPMGMT_DOOR, O_RDONLY)) < 0) { + err = errno; + (void) pthread_mutex_unlock(&iph->iph_lock); + return (err); + } + } + (void) pthread_mutex_unlock(&iph->iph_lock); + + if (door_call(iph->iph_door_fd, &darg) == -1) + return (errno); + err = ((ipmgmt_retval_t *)(void *)(darg.rbuf))->ir_err; + if (darg.rbuf != *rbufp) { + /* + * if the caller is expecting the result to fit in specified + * buffer then return failure. + */ + if (!is_varsize) + err = EBADE; + /* + * The size of the buffer `*rbufp' was not big enough + * and the door itself allocated buffer, for us. We will + * hit this, on several occasion as for some cases + * we cannot predict the size of the return structure. + * Reallocate the buffer `*rbufp' and memcpy() the contents + * to new buffer. + */ + if (err == 0) { + void *newp; + + /* allocated memory will be freed by the caller */ + if ((newp = realloc(*rbufp, darg.rsize)) == NULL) { + err = ENOMEM; + } else { + *rbufp = newp; + (void) memcpy(*rbufp, darg.rbuf, darg.rsize); + } + } + /* munmap() the door buffer */ + (void) munmap(darg.rbuf, darg.rsize); + } else { + if (darg.rsize != rsize) + err = EBADE; + } + return (err); +} diff --git a/usr/src/lib/libipadm/common/libipadm.h b/usr/src/lib/libipadm/common/libipadm.h new file mode 100644 index 0000000000..621c06b4e4 --- /dev/null +++ b/usr/src/lib/libipadm/common/libipadm.h @@ -0,0 +1,346 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _LIBIPADM_H +#define _LIBIPADM_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/types.h> +#include <sys/socket.h> +#include <net/if.h> +#include <netdb.h> +#include <ifaddrs.h> +#include <libnvpair.h> +#include <netinet/tcp.h> +#include <sys/stropts.h> + +#define IPADM_AOBJ_USTRSIZ 32 +#define IPADM_AOBJSIZ (LIFNAMSIZ + IPADM_AOBJ_USTRSIZ) +#define MAXPROPVALLEN 512 +#define LOOPBACK_IF "lo0" + +/* special timeout values for dhcp operations */ +#define IPADM_DHCP_WAIT_DEFAULT (-1) +#define IPADM_DHCP_WAIT_FOREVER (-2) + +/* + * Specifies that the string passed to ipadm_str2nvlist() is a string of comma + * separated names and that each name does not have values associated with it. + */ +#define IPADM_NORVAL 0x00000001 + +/* error codes */ +typedef enum { + IPADM_SUCCESS, /* No error occurred */ + IPADM_FAILURE, /* Generic failure */ + IPADM_EAUTH, /* Insufficient user authorizations */ + IPADM_EPERM, /* Permission denied */ + IPADM_NO_BUFS, /* No Buffer space available */ + IPADM_NO_MEMORY, /* Insufficient memory */ + IPADM_BAD_ADDR, /* Invalid address */ + IPADM_BAD_PROTOCOL, /* Wrong protocol family for operation */ + IPADM_DAD_FOUND, /* Duplicate address detected */ + IPADM_EXISTS, /* Already exists */ + IPADM_IF_EXISTS, /* Interface already exists */ + IPADM_ADDROBJ_EXISTS, /* Address object already exists */ + IPADM_ADDRCONF_EXISTS, /* Addrconf already in progress */ + IPADM_ENXIO, /* Interface does not exist */ + IPADM_GRP_NOTEMPTY, /* IPMP Group non-empty on unplumb */ + IPADM_INVALID_ARG, /* Invalid argument */ + IPADM_INVALID_NAME, /* Invalid name */ + IPADM_DLPI_FAILURE, /* Could not open DLPI link */ + IPADM_DLADM_FAILURE, /* DLADM error encountered */ + IPADM_PROP_UNKNOWN, /* Unknown property */ + IPADM_ERANGE, /* Value is outside the allowed range */ + IPADM_ESRCH, /* Value does not exist */ + IPADM_EOVERFLOW, /* Number of values exceed the allowed limit */ + IPADM_NOTFOUND, /* Object not found */ + IPADM_IF_INUSE, /* Interface already in use */ + IPADM_ADDR_INUSE, /* Address alrelady in use */ + IPADM_BAD_HOSTNAME, /* hostname maps to multiple IP addresses */ + IPADM_ADDR_NOTAVAIL, /* Can't assign requested address */ + IPADM_ALL_ADDRS_NOT_ENABLED, /* All addresses could not be enabled */ + IPADM_NDPD_NOT_RUNNING, /* in.ndpd not running */ + IPADM_DHCP_START_ERROR, /* Cannot start dhcpagent */ + IPADM_DHCP_IPC_ERROR, /* Cannot communicate with dhcpagent */ + IPADM_DHCP_IPC_TIMEOUT, /* Communication with dhcpagent timed out */ + IPADM_TEMPORARY_OBJ, /* Permanent operation on temporary object */ + IPADM_IPC_ERROR, /* Cannot communicate with ipmgmtd */ + IPADM_OP_DISABLE_OBJ, /* Operation on disable object */ + IPADM_NOTSUP, /* Operation not supported */ + IPADM_EBADE /* Invalid data exchange with ipmgmtd */ +} ipadm_status_t; + +/* + * option flags taken by the libipadm functions + * + * - IPADM_OPT_PERSIST: + * For all the create/delete/up/down/set/get functions, + * requests to persist the configuration so that it can be + * re-enabled or reapplied on boot. + * + * - IPADM_OPT_ACTIVE: + * Requests to apply configuration without persisting it and + * used by show-* subcommands to retrieve current values. + * + * - IPADM_OPT_DEFAULT: + * retrieves the default value for a given property + * + * - IPADM_OPT_PERM + * retrieves the permission for a given property + * + * - IPADM_OPT_POSSIBLE + * retrieves the range of values for a given property + * + * - IPADM_OPT_APPEND + * for multi-valued properties, appends a new value. + * + * - IPADM_OPT_REMOVE + * for multi-valued properties, removes the specified value + * + * - IPADM_OPT_IPMP + * Used in ipadm_create_if() to plumb ipmp interfaces. + * + * - IPADM_OPT_GENPPA + * Used in ipadm_create_if() to generate a ppa for the given interface. + * + * - IPADM_OPT_ZEROADDR + * return :: or INADDR_ANY + * + * - IPADM_OPT_RELEASE + * Used to release the lease on a dhcp address object + * + * - IPADM_OPT_INFORM + * Used to perform DHCP_INFORM on a specified static address object + * + * - IPADM_OPT_UP + * Used to bring up a static address on creation + */ +#define IPADM_OPT_PERSIST 0x00000001 +#define IPADM_OPT_ACTIVE 0x00000002 +#define IPADM_OPT_DEFAULT 0x00000004 +#define IPADM_OPT_PERM 0x00000008 +#define IPADM_OPT_POSSIBLE 0x00000010 +#define IPADM_OPT_APPEND 0x00000020 +#define IPADM_OPT_REMOVE 0x00000040 +#define IPADM_OPT_IPMP 0x00000080 +#define IPADM_OPT_GENPPA 0x00000100 +#define IPADM_OPT_ZEROADDR 0x00000200 +#define IPADM_OPT_RELEASE 0x00000400 +#define IPADM_OPT_INFORM 0x00000800 +#define IPADM_OPT_UP 0x00001000 + +/* IPADM property class */ +#define IPADMPROP_CLASS_MODULE 0x00000001 /* on 'protocol' only */ +#define IPADMPROP_CLASS_IF 0x00000002 /* on 'IP interface' only */ +#define IPADMPROP_CLASS_ADDR 0x00000004 /* on 'IP address' only */ +/* protocol property that can be applied on interface too */ +#define IPADMPROP_CLASS_MODIF (IPADMPROP_CLASS_MODULE | IPADMPROP_CLASS_IF) + +/* opaque ipadm handle to libipadm functions */ +struct ipadm_handle; +typedef struct ipadm_handle *ipadm_handle_t; + +/* ipadm_handle flags */ +#define IPH_VRRP 0x00000001 /* Caller is VRRP */ +#define IPH_LEGACY 0x00000002 /* Caller is legacy app */ + +/* opaque address object structure */ +typedef struct ipadm_addrobj_s *ipadm_addrobj_t; + +/* ipadm_if_info_t states */ +typedef enum { + IFIS_OK, /* Interface is usable */ + IFIS_DOWN, /* Interface has no UP addresses */ + IFIS_FAILED, /* Interface has failed. */ + IFIS_OFFLINE, /* Interface has been offlined */ + IFIS_DISABLED /* Interface has been disabled. */ +} ipadm_if_state_t; + +typedef struct ipadm_if_info_s { + struct ipadm_if_info_s *ifi_next; + char ifi_name[LIFNAMSIZ]; /* interface name */ + ipadm_if_state_t ifi_state; /* see above */ + uint_t ifi_cflags; /* current flags */ + uint_t ifi_pflags; /* persistent flags */ +} ipadm_if_info_t; + +/* ipadm_if_info_t flags */ +#define IFIF_BROADCAST 0x00000001 +#define IFIF_MULTICAST 0x00000002 +#define IFIF_POINTOPOINT 0x00000004 +#define IFIF_VIRTUAL 0x00000008 +#define IFIF_IPMP 0x00000010 +#define IFIF_STANDBY 0x00000020 +#define IFIF_INACTIVE 0x00000040 +#define IFIF_VRRP 0x00000080 +#define IFIF_NOACCEPT 0x00000100 +#define IFIF_IPV4 0x00000200 +#define IFIF_IPV6 0x00000400 + +/* ipadm_addr_info_t state */ +typedef enum { + IFA_DISABLED, /* Address not in active configuration. */ + IFA_DUPLICATE, /* DAD failed. */ + IFA_DOWN, /* Address is not IFF_UP */ + IFA_TENTATIVE, /* DAD verification initiated */ + IFA_OK, /* Address is usable */ + IFA_INACCESSIBLE /* Interface has failed */ +} ipadm_addr_state_t; + +/* possible address types */ +typedef enum { + IPADM_ADDR_NONE, + IPADM_ADDR_STATIC, + IPADM_ADDR_IPV6_ADDRCONF, + IPADM_ADDR_DHCP +} ipadm_addr_type_t; + +typedef struct ipadm_addr_info_s { + struct ifaddrs ia_ifa; /* list of addresses */ + char ia_sname[NI_MAXHOST]; /* local hostname */ + char ia_dname[NI_MAXHOST]; /* remote hostname */ + char ia_aobjname[IPADM_AOBJSIZ]; + uint_t ia_cflags; /* active flags */ + uint_t ia_pflags; /* persistent flags */ + ipadm_addr_type_t ia_atype; /* see above */ + ipadm_addr_state_t ia_state; /* see above */ +} ipadm_addr_info_t; +#define IA_NEXT(ia) ((ipadm_addr_info_t *)(ia->ia_ifa.ifa_next)) + +/* ipadm_addr_info_t flags */ +#define IA_UP 0x00000001 +#define IA_UNNUMBERED 0x00000002 +#define IA_PRIVATE 0x00000004 +#define IA_TEMPORARY 0x00000008 +#define IA_DEPRECATED 0x00000010 + +/* open/close libipadm handle */ +extern ipadm_status_t ipadm_open(ipadm_handle_t *, uint32_t); +extern void ipadm_close(ipadm_handle_t); + +/* Check authorization for network configuration */ +extern boolean_t ipadm_check_auth(void); +/* + * Interface mangement functions + */ +extern ipadm_status_t ipadm_create_if(ipadm_handle_t, char *, sa_family_t, + uint32_t); +extern ipadm_status_t ipadm_disable_if(ipadm_handle_t, const char *, + uint32_t); +extern ipadm_status_t ipadm_enable_if(ipadm_handle_t, const char *, uint32_t); +extern ipadm_status_t ipadm_if_info(ipadm_handle_t, const char *, + ipadm_if_info_t **, uint32_t, int64_t); +extern void ipadm_free_if_info(ipadm_if_info_t *); +extern ipadm_status_t ipadm_delete_if(ipadm_handle_t, const char *, + sa_family_t, uint32_t); + +/* + * Address management functions + */ +extern ipadm_status_t ipadm_create_addr(ipadm_handle_t, ipadm_addrobj_t, + uint32_t); +extern ipadm_status_t ipadm_disable_addr(ipadm_handle_t, const char *, + uint32_t); +extern ipadm_status_t ipadm_enable_addr(ipadm_handle_t, const char *, + uint32_t); +extern ipadm_status_t ipadm_addr_info(ipadm_handle_t, const char *, + ipadm_addr_info_t **, uint32_t, int64_t); +extern void ipadm_free_addr_info(ipadm_addr_info_t *); +extern ipadm_status_t ipadm_up_addr(ipadm_handle_t, const char *, + uint32_t); +extern ipadm_status_t ipadm_down_addr(ipadm_handle_t, const char *, + uint32_t); +extern ipadm_status_t ipadm_refresh_addr(ipadm_handle_t, const char *, + uint32_t); +extern ipadm_status_t ipadm_delete_addr(ipadm_handle_t, const char *, + uint32_t); + +/* Functions related to creating/deleting/modifying opaque address object */ +extern ipadm_status_t ipadm_create_addrobj(ipadm_addr_type_t, const char *, + ipadm_addrobj_t *); +extern void ipadm_destroy_addrobj(ipadm_addrobj_t); + +/* Functions to set fields in addrobj for static addresses */ +extern ipadm_status_t ipadm_set_addr(ipadm_addrobj_t, const char *, + sa_family_t); +extern ipadm_status_t ipadm_set_dst_addr(ipadm_addrobj_t, const char *, + sa_family_t); + +/* Functions to set fields in addrobj for IPv6 addrconf */ +extern ipadm_status_t ipadm_set_interface_id(ipadm_addrobj_t, const char *); +extern ipadm_status_t ipadm_set_stateless(ipadm_addrobj_t, boolean_t); +extern ipadm_status_t ipadm_set_stateful(ipadm_addrobj_t, boolean_t); + +/* Functions to set fields in addrobj for DHCP */ +extern ipadm_status_t ipadm_set_primary(ipadm_addrobj_t, boolean_t); +extern ipadm_status_t ipadm_set_wait_time(ipadm_addrobj_t, int32_t); + +/* + * Property management functions + */ +/* call back function for the property walker */ +typedef boolean_t ipadm_prop_wfunc_t(void *, const char *, uint_t); +extern ipadm_status_t ipadm_walk_proptbl(uint_t, uint_t, ipadm_prop_wfunc_t *, + void *); +extern ipadm_status_t ipadm_walk_prop(const char *, uint_t, uint_t, + ipadm_prop_wfunc_t *, void *); + +/* Interface property management - set, reset and get */ +extern ipadm_status_t ipadm_set_ifprop(ipadm_handle_t, const char *, + const char *, const char *, uint_t, uint_t); +extern ipadm_status_t ipadm_get_ifprop(ipadm_handle_t, const char *, + const char *, char *, uint_t *, uint_t, uint_t); + +/* Address property management - set, reset and get */ +extern ipadm_status_t ipadm_set_addrprop(ipadm_handle_t, const char *, + const char *, const char *, uint_t); +extern ipadm_status_t ipadm_get_addrprop(ipadm_handle_t, const char *, char *, + uint_t *, const char *, uint_t); + +/* Protoocl property management - set, reset and get */ +extern ipadm_status_t ipadm_set_prop(ipadm_handle_t, const char *, + const char *, uint_t, uint_t); +extern ipadm_status_t ipadm_get_prop(ipadm_handle_t, const char *, char *, + uint_t *, uint_t, uint_t); +extern ipadm_status_t ipadm_init_prop(void); + +/* + * miscellaneous helper functions. + */ +extern const char *ipadm_status2str(ipadm_status_t); +extern int ipadm_str2nvlist(const char *, nvlist_t **, uint_t); +extern size_t ipadm_nvlist2str(nvlist_t *, char *, size_t); +extern char *ipadm_proto2str(uint_t); +extern uint_t ipadm_str2proto(const char *); +extern ipadm_status_t ipadm_open_arp_on_udp(const char *, int *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBIPADM_H */ diff --git a/usr/src/lib/libipadm/common/libipadm_impl.h b/usr/src/lib/libipadm/common/libipadm_impl.h new file mode 100644 index 0000000000..6572bc64b7 --- /dev/null +++ b/usr/src/lib/libipadm/common/libipadm_impl.h @@ -0,0 +1,227 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ + +#ifndef _LIBIPADM_IMPL_H +#define _LIBIPADM_IMPL_H + +#ifdef __cplusplus +extern "C" { +#endif + +#include <sys/socket.h> +#include <net/if.h> +#include <libipadm.h> +#include <libdladm.h> +#include <ipadm_ipmgmt.h> +#include <inet/tunables.h> +#include <netinet/in.h> +#include <pthread.h> +#include <libinetutil.h> +#include <libsocket_priv.h> + +#define IPADM_STRSIZE 256 +#define IPADM_ONSTR "on" +#define IPADM_OFFSTR "off" +#define ARP_MOD_NAME "arp" +#define IPADM_LOGICAL_SEP ':' +#define IPV6_MIN_MTU 1280 /* rfc2460 */ + +/* mask for flags accepted by libipadm functions */ +#define IPADM_COMMON_OPT_MASK (IPADM_OPT_ACTIVE | IPADM_OPT_PERSIST) + +/* Opaque library handle */ +struct ipadm_handle { + int iph_sock; /* socket to interface */ + int iph_sock6; /* socket to interface */ + int iph_door_fd; /* door descriptor to ipmgmtd */ + dladm_handle_t iph_dlh; /* handle to libdladm library */ + uint32_t iph_flags; /* internal flags */ + pthread_mutex_t iph_lock; /* lock to set door_fd */ +}; + +/* + * Indicates that the operation being invoked is in 'init' context. This is + * a library private flag. + */ +#define IPH_INIT 0x10000000 + +struct ipadm_addrobj_s { + char ipadm_ifname[LIFNAMSIZ]; + int32_t ipadm_lifnum; + char ipadm_aobjname[IPADM_AOBJSIZ]; + ipadm_addr_type_t ipadm_atype; + uint32_t ipadm_flags; + sa_family_t ipadm_af; + union { + struct { + char ipadm_ahname[MAXNAMELEN]; + struct sockaddr_storage ipadm_addr; + uint32_t ipadm_prefixlen; + char ipadm_dhname[MAXNAMELEN]; + struct sockaddr_storage ipadm_dstaddr; + } ipadm_static_addr_s; + struct { + struct sockaddr_in6 ipadm_intfid; + uint32_t ipadm_intfidlen; + boolean_t ipadm_stateless; + boolean_t ipadm_stateful; + } ipadm_ipv6_intfid_s; + struct { + boolean_t ipadm_primary; + int32_t ipadm_wait; + } ipadm_dhcp_s; + } ipadm_addr_u; +}; + +#define ipadm_static_addr ipadm_addr_u.ipadm_static_addr_s.ipadm_addr +#define ipadm_static_aname ipadm_addr_u.ipadm_static_addr_s.ipadm_ahname +#define ipadm_static_prefixlen ipadm_addr_u.ipadm_static_addr_s.ipadm_prefixlen +#define ipadm_static_dst_addr ipadm_addr_u.ipadm_static_addr_s.ipadm_dstaddr +#define ipadm_static_dname ipadm_addr_u.ipadm_static_addr_s.ipadm_dhname +#define ipadm_intfid ipadm_addr_u.ipadm_ipv6_intfid_s.ipadm_intfid +#define ipadm_intfidlen ipadm_addr_u.ipadm_ipv6_intfid_s.ipadm_intfidlen +#define ipadm_stateless ipadm_addr_u.ipadm_ipv6_intfid_s.ipadm_stateless +#define ipadm_stateful ipadm_addr_u.ipadm_ipv6_intfid_s.ipadm_stateful +#define ipadm_primary ipadm_addr_u.ipadm_dhcp_s.ipadm_primary +#define ipadm_wait ipadm_addr_u.ipadm_dhcp_s.ipadm_wait + +/* + * Data structures and callback functions related to property management + */ +struct ipadm_prop_desc; +typedef struct ipadm_prop_desc ipadm_prop_desc_t; + +/* property set() callback */ +typedef ipadm_status_t ipadm_pd_setf_t(ipadm_handle_t, const void *, + ipadm_prop_desc_t *, const void *, uint_t, uint_t); + +/* property get() callback */ +typedef ipadm_status_t ipadm_pd_getf_t(ipadm_handle_t, const void *, + ipadm_prop_desc_t *, char *, uint_t *, uint_t, uint_t); + +struct ipadm_prop_desc { + char *ipd_name; /* property name */ + uint_t ipd_class; /* prop. class - global/perif/both */ + uint_t ipd_proto; /* protocol to which property belongs */ + ipadm_pd_setf_t *ipd_set; /* set callback function */ + ipadm_pd_getf_t *ipd_get_range; /* get range callback function */ + ipadm_pd_getf_t *ipd_get; /* get value callback function */ +}; + +extern ipadm_prop_desc_t ipadm_addrprop_table[]; +extern ipadm_pd_getf_t i_ipadm_get_onoff; + +/* libipadm.c */ +extern ipadm_status_t i_ipadm_get_flags(ipadm_handle_t, const char *, + sa_family_t, uint64_t *); +extern ipadm_status_t i_ipadm_set_flags(ipadm_handle_t, const char *, + sa_family_t, uint64_t, uint64_t); +extern ipadm_status_t i_ipadm_init_ifs(ipadm_handle_t, const char *, + nvlist_t **); +extern ipadm_status_t i_ipadm_init_ifobj(ipadm_handle_t, const char *, + nvlist_t *); +extern ipadm_status_t i_ipadm_init_addrobj(ipadm_handle_t, nvlist_t *); +extern ipadm_status_t i_ipadm_addr_persist(ipadm_handle_t, + const ipadm_addrobj_t, boolean_t, uint32_t); +extern ipadm_status_t i_ipadm_delete_addr(ipadm_handle_t, ipadm_addrobj_t); +extern int i_ipadm_strioctl(int, int, char *, int); +extern boolean_t i_ipadm_is_loopback(const char *); +extern boolean_t i_ipadm_is_vni(const char *); +extern boolean_t i_ipadm_is_ipmp(ipadm_handle_t, const char *); +extern boolean_t i_ipadm_is_under_ipmp(ipadm_handle_t, const char *); +extern boolean_t i_ipadm_is_6to4(ipadm_handle_t, char *); +extern boolean_t i_ipadm_validate_ifname(ipadm_handle_t, const char *); +extern ipadm_status_t ipadm_errno2status(int); +extern int ipadm_door_call(ipadm_handle_t, void *, size_t, void **, + size_t, boolean_t); +extern boolean_t ipadm_if_enabled(ipadm_handle_t, const char *, + sa_family_t); + +/* ipadm_ndpd.c */ +extern ipadm_status_t i_ipadm_create_ipv6addrs(ipadm_handle_t, + ipadm_addrobj_t, uint32_t); +extern ipadm_status_t i_ipadm_delete_ipv6addrs(ipadm_handle_t, + ipadm_addrobj_t); +extern ipadm_status_t i_ipadm_disable_autoconf(const char *); +extern ipadm_status_t i_ipadm_enable_autoconf(const char *); + +/* ipadm_persist.c */ +extern ipadm_status_t i_ipadm_add_ipaddr2nvl(nvlist_t *, ipadm_addrobj_t); +extern ipadm_status_t i_ipadm_add_ip6addr2nvl(nvlist_t *, ipadm_addrobj_t); +extern ipadm_status_t i_ipadm_add_intfid2nvl(nvlist_t *, ipadm_addrobj_t); +extern ipadm_status_t i_ipadm_add_dhcp2nvl(nvlist_t *, boolean_t, int32_t); + +/* ipadm_prop.c */ +extern ipadm_status_t i_ipadm_persist_propval(ipadm_handle_t, + ipadm_prop_desc_t *, const char *, const void *, + uint_t); +extern ipadm_status_t i_ipadm_get_persist_propval(ipadm_handle_t, + ipadm_prop_desc_t *, char *, uint_t *, + const void *); + +/* ipadm_addr.c */ +extern void i_ipadm_init_addr(ipadm_addrobj_t, const char *, + const char *, ipadm_addr_type_t); +extern ipadm_status_t i_ipadm_merge_prefixlen_from_nvl(nvlist_t *, nvlist_t *, + const char *); +extern ipadm_status_t i_ipadm_get_addrobj(ipadm_handle_t, ipadm_addrobj_t); +extern ipadm_status_t i_ipadm_enable_static(ipadm_handle_t, const char *, + nvlist_t *, sa_family_t); +extern ipadm_status_t i_ipadm_enable_dhcp(ipadm_handle_t, const char *, + nvlist_t *); +extern ipadm_status_t i_ipadm_enable_addrconf(ipadm_handle_t, const char *, + nvlist_t *); +extern void i_ipadm_addrobj2lifname(ipadm_addrobj_t, char *, int); +extern ipadm_status_t i_ipadm_nvl2in6_addr(nvlist_t *, char *, + in6_addr_t *); +extern ipadm_status_t i_ipadm_get_lif2addrobj(ipadm_handle_t, + ipadm_addrobj_t); +extern ipadm_status_t i_ipadm_lookupadd_addrobj(ipadm_handle_t, + ipadm_addrobj_t); +extern ipadm_status_t i_ipadm_delete_addrobj(ipadm_handle_t, + const ipadm_addrobj_t, uint32_t); +extern boolean_t i_ipadm_name2atype(const char *, sa_family_t *, + ipadm_addr_type_t *); + +/* ipadm_if.c */ +extern ipadm_status_t i_ipadm_create_if(ipadm_handle_t, char *, sa_family_t, + uint32_t); +extern ipadm_status_t i_ipadm_delete_if(ipadm_handle_t, const char *, + sa_family_t, uint32_t); +extern ipadm_status_t i_ipadm_plumb_if(ipadm_handle_t, char *, sa_family_t, + uint32_t); +extern ipadm_status_t i_ipadm_unplumb_if(ipadm_handle_t, const char *, + sa_family_t); +extern ipadm_status_t i_ipadm_if_pexists(ipadm_handle_t, const char *, + sa_family_t, boolean_t *); +extern ipadm_status_t i_ipadm_delete_ifobj(ipadm_handle_t, const char *, + sa_family_t, boolean_t); +extern int i_ipadm_get_lnum(const char *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBIPADM_IMPL_H */ diff --git a/usr/src/lib/libipadm/common/llib-lipadm b/usr/src/lib/libipadm/common/llib-lipadm new file mode 100644 index 0000000000..4553567250 --- /dev/null +++ b/usr/src/lib/libipadm/common/llib-lipadm @@ -0,0 +1,35 @@ +/* + * 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. + */ + +/*LINTLIBRARY*/ +/*PROTOLIB1*/ + +#include <libipadm.h> +/* + * functions private to libipadm and ipmgmtd/in.ndpd are prototyped + * in the files below + */ +#include <ipadm_ipmgmt.h> +#include <ipadm_ndpd.h> diff --git a/usr/src/lib/libipadm/common/mapfile-vers b/usr/src/lib/libipadm/common/mapfile-vers new file mode 100644 index 0000000000..9a48e57547 --- /dev/null +++ b/usr/src/lib/libipadm/common/mapfile-vers @@ -0,0 +1,93 @@ +# +# 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. +# + + +# +# MAPFILE HEADER START +# +# WARNING: STOP NOW. DO NOT MODIFY THIS FILE. +# Object versioning must comply with the rules detailed in +# +# usr/src/lib/README.mapfiles +# +# You should not be making modifications here until you've read the most current +# copy of that file. If you need help, contact a gatekeeper for guidance. +# +# MAPFILE HEADER END +# + +SUNWprivate_1.1 { + global: + ipadm_add_aobjname; + ipadm_addr_info; + ipadm_check_auth; + ipadm_close; + ipadm_create_addr; + ipadm_create_addrobj; + ipadm_create_if; + ipadm_delete_addr; + ipadm_delete_aobjname; + ipadm_delete_if; + ipadm_destroy_addrobj; + ipadm_disable_addr; + ipadm_disable_if; + ipadm_down_addr; + ipadm_enable_addr; + ipadm_enable_if; + ipadm_free_addr_info; + ipadm_free_if_info; + ipadm_get_addrprop; + ipadm_get_ifprop; + ipadm_get_prop; + ipadm_if_enabled; + ipadm_if_info; + ipadm_init_prop; + ipadm_ndpd_read; + ipadm_ndpd_write; + ipadm_nvlist2str; + ipadm_open; + ipadm_open_arp_on_udp; + ipadm_proto2str; + ipadm_refresh_addr; + ipadm_rw_db; + ipadm_set_addr; + ipadm_set_addrprop; + ipadm_set_dst_addr; + ipadm_set_ifprop; + ipadm_set_interface_id; + ipadm_set_primary; + ipadm_set_prop; + ipadm_set_stateful; + ipadm_set_stateless; + ipadm_set_wait_time; + ipadm_status2str; + ipadm_str2nvlist; + ipadm_str2proto; + ipadm_up_addr; + ipadm_walk_prop; + ipadm_walk_proptbl; + local: + *; +}; diff --git a/usr/src/lib/libipadm/i386/Makefile b/usr/src/lib/libipadm/i386/Makefile new file mode 100644 index 0000000000..4d9d4abd5b --- /dev/null +++ b/usr/src/lib/libipadm/i386/Makefile @@ -0,0 +1,28 @@ +# +# 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 ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libipadm/libipadm.xcl b/usr/src/lib/libipadm/libipadm.xcl new file mode 100644 index 0000000000..d8877dd4ca --- /dev/null +++ b/usr/src/lib/libipadm/libipadm.xcl @@ -0,0 +1,89 @@ +# +# 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. +# + +msgid "" +msgid "%c%c" +msgid "%d" +msgid "%d,%s" +msgid "%s" +msgid "%s%d" +msgid "%s,%s" +msgid "%s/%s" +msgid "%s/%s.new" +msgid "%s0" +msgid "%s:%d" +msgid "%s=" +msgid "%s=%s" +msgid "%u" +msgid "," +msgid ",%s" +msgid "/" +msgid "/%d,%s,%s" +msgid "0" +msgid "1" +msgid "1-126,128" +msgid "1-30,32" +msgid "active" +msgid "all-zones" +msgid "arp" +msgid "broadcast" +msgid "deprecated" +msgid "ecn" +msgid "exchange_routes" +msgid "extra_priv_ports" +msgid "forwarding" +msgid "hoplimit" +msgid "icmp" +msgid "ip" +msgid "ipmpstub0" +msgid "ipv4" +msgid "ipv6" +msgid "largest_anon_port" +msgid "metric" +msgid "mtu" +msgid "never" +msgid "no" +msgid "nud" +msgid "passive" +msgid "prefixlen" +msgid "private" +msgid "r" +msgid "r+" +msgid "recv_maxbuf" +msgid "sack" +msgid "sctp" +msgid "send_maxbuf" +msgid "smallest_anon_port" +msgid "smallest_nonpriv_port" +msgid "tcp" +msgid "transmit" +msgid "ttl" +msgid "udp" +msgid "up" +msgid "usesrc" +msgid "vni" +msgid "w" +msgid "yes" +msgid "zone" diff --git a/usr/src/lib/libipadm/sparc/Makefile b/usr/src/lib/libipadm/sparc/Makefile new file mode 100644 index 0000000000..8069eadad1 --- /dev/null +++ b/usr/src/lib/libipadm/sparc/Makefile @@ -0,0 +1,29 @@ +# +# 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 ../Makefile.com + +install: all $(ROOTLIBS) $(ROOTLINKS) $(ROOTLINT) diff --git a/usr/src/lib/libsecdb/auth_attr.txt b/usr/src/lib/libsecdb/auth_attr.txt index 0a10c10a11..7d5ef346c6 100644 --- a/usr/src/lib/libsecdb/auth_attr.txt +++ b/usr/src/lib/libsecdb/auth_attr.txt @@ -97,6 +97,7 @@ solaris.network.autoconf.wlan:::Create Network Auto-Magic Config for Known WLANs solaris.network.autoconf.write:::Create Network Auto-Magic Config::help=NetworkAutoconfWrite.html solaris.network.ilb.config:::Network ILB Configuration::help=NetworkILBconf.html solaris.network.ilb.enable:::Network ILB Enable Configuration::help=NetworkILBenable.html +solaris.network.interface.config:::Network Interface Configuration::help=NetworkInterfaceConfig.html solaris.network.link.security:::Link Security::help=LinkSecurity.html solaris.network.wifi.config:::Wifi Config::help=WifiConfig.html solaris.network.wifi.wep:::Wifi Wep::help=WifiWep.html diff --git a/usr/src/lib/libsecdb/exec_attr.txt b/usr/src/lib/libsecdb/exec_attr.txt index db47b15bd0..b7cfde648e 100644 --- a/usr/src/lib/libsecdb/exec_attr.txt +++ b/usr/src/lib/libsecdb/exec_attr.txt @@ -175,6 +175,8 @@ Network Management:solaris:cmd:::/sbin/dlstat:euid=dladm;egid=sys; Network Management:solaris:cmd:::/sbin/flowadm:euid=dladm;egid=sys;\ privs=sys_dl_config,net_rawaccess,proc_audit Network Management:solaris:cmd:::/sbin/flowstat:euid=dladm;egid=sys; +Network Management:solaris:cmd:::/sbin/ipadm:euid=netadm;egid=netadm;\ + privs=sys_ip_config,net_rawaccess Network Management:suser:cmd:::/usr/bin/netstat:uid=0 Network Management:suser:cmd:::/usr/bin/rup:euid=0 Network Management:suser:cmd:::/usr/bin/ruptime:euid=0 diff --git a/usr/src/lib/libsecdb/help/auths/Makefile b/usr/src/lib/libsecdb/help/auths/Makefile index cb9361c94e..6b1bb7737b 100644 --- a/usr/src/lib/libsecdb/help/auths/Makefile +++ b/usr/src/lib/libsecdb/help/auths/Makefile @@ -129,6 +129,7 @@ HTMLENTS = \ NetworkILBenable.html \ NetworkHeader.html \ NetworkVRRP.html \ + NetworkInterfaceConfig.html \ WifiConfig.html \ WifiWep.html \ LinkSecurity.html \ diff --git a/usr/src/lib/libsecdb/help/auths/NetworkInterfaceConfig.html b/usr/src/lib/libsecdb/help/auths/NetworkInterfaceConfig.html new file mode 100644 index 0000000000..afc2f5ebd1 --- /dev/null +++ b/usr/src/lib/libsecdb/help/auths/NetworkInterfaceConfig.html @@ -0,0 +1,42 @@ +<html> + +<!-- + Copyright 2010 Sun Microsystems, Inc. All rights reserved. + Use is subject to license terms. + + 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 +--> + +<head> +<!-- +meta http-equiv="Content-Type" content="text/html; charset=iso-8859-1" +--> +</head> +<body> +When Network Interface Configuration is in the Authorizations +Included column, it grants permission to create/delete/show network IP +interfaces on data-links. It also grants permission to set/reset/get tunables on +protocol modules. +<p> +Note that querying configuration information on Network IP interfaces or the +protocol module properties doesn't require the Network Interface Configuration +authorization. +</body> +</html> diff --git a/usr/src/lib/libsecdb/prof_attr.txt b/usr/src/lib/libsecdb/prof_attr.txt index a8d5fc56b6..a1b5961d09 100644 --- a/usr/src/lib/libsecdb/prof_attr.txt +++ b/usr/src/lib/libsecdb/prof_attr.txt @@ -65,7 +65,7 @@ Network Autoconf Admin:::Manage Network Auto-Magic configuration via nwamd:profi Network Autoconf User:::Network Auto-Magic User:auths=solaris.network.autoconf.read,solaris.network.autoconf.select,solaris.network.autoconf.wlan;help=RtNetAutoconfUser.html Network ILB:::Manage ILB configuration via ilbadm:auths=solaris.network.ilb.config,solaris.network.ilb.enable;help=RtNetILB.html Network VRRP:::Manage VRRP instances:auths=solaris.network.vrrp,solaris.smf.manage.vrrp;help=RtNetVRRP.html -Network Management:::Manage the host and network configuration:auths=solaris.smf.manage.name-service-cache,solaris.smf.manage.bind,solaris.smf.value.routing,solaris.smf.manage.routing,solaris.smf.value.nwam,solaris.smf.manage.nwam,solaris.smf.manage.tnd,solaris.smf.manage.tnctl,solaris.smf.manage.wpa,solaris.smf.value.mdns,solaris.smf.manage.mdns,solaris.smf.manage.ilb;profiles=Network Wifi Management,Inetd Management,Network VRRP,Network Observability;help=RtNetMngmnt.html +Network Management:::Manage the host and network configuration:auths=solaris.smf.manage.name-service-cache,solaris.smf.manage.bind,solaris.smf.value.routing,solaris.smf.manage.routing,solaris.smf.value.nwam,solaris.smf.manage.nwam,solaris.smf.manage.tnd,solaris.smf.manage.tnctl,solaris.smf.manage.wpa,solaris.smf.value.mdns,solaris.smf.manage.mdns,solaris.smf.manage.ilb,solaris.network.interface.config;profiles=Network Wifi Management,Inetd Management,Network VRRP,Network Observability;help=RtNetMngmnt.html Network Observability:::Allow access to observability devices:privs=net_observability;help=RtNetObservability.html Network Security:::Manage network and host security:auths=solaris.smf.manage.ssh,solaris.smf.value.tnd,solaris.network.*;profiles=Network Wifi Security,Network Link Security,Network IPsec Management;help=RtNetSecure.html Network Wifi Management:::Manage wifi network configuration:auths=solaris.network.wifi.config;help=RtNetWifiMngmnt.html diff --git a/usr/src/lib/libsocket/Makefile b/usr/src/lib/libsocket/Makefile index 18d1f13290..763363b7fd 100644 --- a/usr/src/lib/libsocket/Makefile +++ b/usr/src/lib/libsocket/Makefile @@ -18,15 +18,14 @@ # # CDDL HEADER END # -# -# Copyright 2006 Sun Microsystems, Inc. All rights reserved. +# Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # -# ident "%Z%%M% %I% %E% SMI" -# include ../Makefile.lib +HDRS = libsocket_priv.h +HDRDIR = common SUBDIRS = $(MACH) $(BUILD64)SUBDIRS += $(MACH64) @@ -45,6 +44,10 @@ TEXT_DOMAIN = SUNW_OST_NETRPC all clean clobber install lint: $(SUBDIRS) +install_h: $(ROOTHDRS) + +check: $(CHECKHDRS) + _msg: $(MSGDOMAINPOFILE) $(POFILE): pofile_MSGFILES @@ -54,4 +57,5 @@ $(SUBDIRS): FRC FRC: +include $(SRC)/lib/Makefile.targ include $(SRC)/Makefile.msg.targ diff --git a/usr/src/lib/libsocket/Makefile.com b/usr/src/lib/libsocket/Makefile.com index cdb99117c5..f1e7a0af99 100644 --- a/usr/src/lib/libsocket/Makefile.com +++ b/usr/src/lib/libsocket/Makefile.com @@ -20,7 +20,7 @@ # # -# Copyright 2008 Sun Microsystems, Inc. All rights reserved. +# Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # @@ -32,7 +32,8 @@ INETOBJS = bindresvport.o bootparams_getbyname.o ether_addr.o \ getprotoent.o getprotoent_r.o getservbyname_r.o getservent.o \ getservent_r.o inet_lnaof.o inet_mkaddr.o inet_network.o \ inet6_opt.o inet6_rthdr.o interface_id.o link_addr.o \ - netmasks.o rcmd.o rexec.o ruserpass.o sourcefilter.o + netmasks.o rcmd.o rexec.o ruserpass.o sourcefilter.o \ + getifaddrs.o SOCKOBJS = _soutil.o sockatmark.o socket.o socketpair.o weaks.o OBJECTS = $(INETOBJS) $(SOCKOBJS) diff --git a/usr/src/lib/libsocket/common/libsocket_priv.h b/usr/src/lib/libsocket/common/libsocket_priv.h new file mode 100644 index 0000000000..8d84ffb5da --- /dev/null +++ b/usr/src/lib/libsocket/common/libsocket_priv.h @@ -0,0 +1,43 @@ +/* + * CDDL HEADER START + * + * The contents of this file are subject to the terms of the + * Common Development and Distribution License (the "License"). + * You may not use this file except in compliance with the License. + * + * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE + * or http://www.opensolaris.org/os/licensing. + * See the License for the specific language governing permissions + * and limitations under the License. + * + * When distributing Covered Code, include this CDDL HEADER in each + * file and include the License file at usr/src/OPENSOLARIS.LICENSE. + * If applicable, add the following below this CDDL HEADER, with the + * fields enclosed by brackets "[]" replaced with your own identifying + * information: Portions Copyright [yyyy] [name of copyright owner] + * + * CDDL HEADER END + */ +/* + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. + * Use is subject to license terms. + */ +#ifndef _LIBSOCKET_PRIV_H +#define _LIBSOCKET_PRIV_H + +#include <net/if.h> +#include <ifaddrs.h> + +#ifdef __cplusplus +extern "C" { +#endif + +extern int getallifaddrs(sa_family_t, struct ifaddrs **, int64_t); +extern int getallifs(int, sa_family_t, struct lifreq **, int *, int64_t); +extern int getnetmaskbyaddr(const struct in_addr, struct in_addr *); + +#ifdef __cplusplus +} +#endif + +#endif /* _LIBSOCKET_PRIV_H */ diff --git a/usr/src/lib/libsocket/common/llib-lsocket b/usr/src/lib/libsocket/common/llib-lsocket index f74c8cea92..104dc78235 100644 --- a/usr/src/lib/libsocket/common/llib-lsocket +++ b/usr/src/lib/libsocket/common/llib-lsocket @@ -2,9 +2,8 @@ * CDDL HEADER START * * The contents of this file are subject to the terms of the - * Common Development and Distribution License, Version 1.0 only - * (the "License"). You may not use this file except in compliance - * with the License. + * 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. @@ -20,10 +19,9 @@ * CDDL HEADER END */ /* - * Copyright 2004 Sun Microsystems, Inc. All rights reserved. + * Copyright 2010 Sun Microsystems, Inc. All rights reserved. * Use is subject to license terms. */ -#pragma ident "%Z%%M% %I% %E% SMI" /* LINTLIBRARY */ /* PROTOLIB1 */ @@ -38,6 +36,8 @@ #include <sys/ethernet.h> #include <netdb.h> #include <net/if_dl.h> +#include <ifaddrs.h> +#include <libsocket_priv.h> /* * usr/src/lib/libsocket/inet routines not prototyped in the above @@ -56,7 +56,6 @@ struct in6_addr *__inet6_rthdr_getaddr(void *, int); /* netmasks.c */ int getnetmaskbynet(const struct in_addr net, struct in_addr *mask); -int getnetmaskbyaddr(const struct in_addr addr, struct in_addr *mask); /* ruserpass.c */ void _ruserpass(const char *host, char **aname, char **apass); diff --git a/usr/src/lib/libsocket/common/mapfile-vers b/usr/src/lib/libsocket/common/mapfile-vers index 6839f8bb04..b71b64968d 100644 --- a/usr/src/lib/libsocket/common/mapfile-vers +++ b/usr/src/lib/libsocket/common/mapfile-vers @@ -19,7 +19,7 @@ # CDDL HEADER END # # -# Copyright 2009 Sun Microsystems, Inc. All rights reserved. +# Copyright 2010 Sun Microsystems, Inc. All rights reserved. # Use is subject to license terms. # @@ -37,6 +37,12 @@ # MAPFILE HEADER END # +SUNW_1.7 { + global: + freeifaddrs; + getifaddrs; +} SUNW_1.6; + SUNW_1.6 { global: getipv4sourcefilter; @@ -157,6 +163,8 @@ SUNWprivate_1.3 { _nss_initf_netmasks; _nss_initf_proto; _nss_initf_services; + getallifaddrs; + getallifs; str2ether; str2addr; str2netent; diff --git a/usr/src/lib/libsocket/inet/getifaddrs.c b/usr/src/lib/libsocket/inet/getifaddrs.c new file mode 100644 index 0000000000..893e83f730 --- /dev/null +++ b/usr/src/lib/libsocket/inet/getifaddrs.c @@ -0,0 +1,269 @@ +/* + * 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 <netdb.h> +#include <nss_dbdefs.h> +#include <netinet/in.h> +#include <sys/socket.h> +#include <string.h> +#include <stdio.h> +#include <sys/sockio.h> +#include <sys/types.h> +#include <stdlib.h> +#include <net/if.h> +#include <ifaddrs.h> +#include <libsocket_priv.h> + +/* + * Create a linked list of `struct ifaddrs' structures, one for each + * address that is UP. If successful, store the list in *ifap and + * return 0. On errors, return -1 and set `errno'. + * + * The storage returned in *ifap is allocated dynamically and can + * only be properly freed by passing it to `freeifaddrs'. + */ +int +getifaddrs(struct ifaddrs **ifap) +{ + int err; + char *cp; + struct ifaddrs *curr; + + if (ifap == NULL) { + errno = EINVAL; + return (-1); + } + *ifap = NULL; + err = getallifaddrs(AF_UNSPEC, ifap, LIFC_ENABLED); + if (err == 0) { + for (curr = *ifap; curr != NULL; curr = curr->ifa_next) { + if ((cp = strchr(curr->ifa_name, ':')) != NULL) + *cp = '\0'; + } + } + return (err); +} + +void +freeifaddrs(struct ifaddrs *ifa) +{ + struct ifaddrs *curr; + + while (ifa != NULL) { + curr = ifa; + ifa = ifa->ifa_next; + free(curr->ifa_name); + free(curr->ifa_addr); + free(curr->ifa_netmask); + free(curr->ifa_dstaddr); + free(curr); + } +} + +/* + * Returns all addresses configured on the system. If flags contain + * LIFC_ENABLED, only the addresses that are UP are returned. + * Address list that is returned by this function must be freed + * using freeifaddrs(). + */ +int +getallifaddrs(sa_family_t af, struct ifaddrs **ifap, int64_t flags) +{ + struct lifreq *buf = NULL; + struct lifreq *lifrp; + struct lifreq lifrl; + int ret; + int s, n, numifs; + struct ifaddrs *curr, *prev; + sa_family_t lifr_af; + int sock4; + int sock6; + int err; + + if ((sock4 = socket(AF_INET, SOCK_DGRAM, 0)) < 0) + return (-1); + if ((sock6 = socket(AF_INET6, SOCK_DGRAM, 0)) < 0) { + err = errno; + close(sock4); + errno = err; + return (-1); + } + +retry: + /* Get all interfaces from SIOCGLIFCONF */ + ret = getallifs(sock4, af, &buf, &numifs, (flags & ~LIFC_ENABLED)); + if (ret != 0) + goto fail; + + /* + * Loop through the interfaces obtained from SIOCGLIFCOMF + * and retrieve the addresses, netmask and flags. + */ + prev = NULL; + lifrp = buf; + *ifap = NULL; + for (n = 0; n < numifs; n++, lifrp++) { + + /* Prepare for the ioctl call */ + (void) strncpy(lifrl.lifr_name, lifrp->lifr_name, + sizeof (lifrl.lifr_name)); + lifr_af = lifrp->lifr_addr.ss_family; + if (af != AF_UNSPEC && lifr_af != af) + continue; + + s = (lifr_af == AF_INET ? sock4 : sock6); + + if (ioctl(s, SIOCGLIFFLAGS, (caddr_t)&lifrl) < 0) + goto fail; + if ((flags & LIFC_ENABLED) && !(lifrl.lifr_flags & IFF_UP)) + continue; + + /* + * Allocate the current list node. Each node contains data + * for one ifaddrs structure. + */ + curr = calloc(1, sizeof (struct ifaddrs)); + if (curr == NULL) + goto fail; + + if (prev != NULL) { + prev->ifa_next = curr; + } else { + /* First node in the linked list */ + *ifap = curr; + } + prev = curr; + + curr->ifa_flags = lifrl.lifr_flags; + if ((curr->ifa_name = strdup(lifrp->lifr_name)) == NULL) + goto fail; + + curr->ifa_addr = malloc(sizeof (struct sockaddr_storage)); + if (curr->ifa_addr == NULL) + goto fail; + *curr->ifa_addr = lifrp->lifr_addr; + + /* Get the netmask */ + if (ioctl(s, SIOCGLIFNETMASK, (caddr_t)&lifrl) < 0) + goto fail; + curr->ifa_netmask = malloc(sizeof (struct sockaddr_storage)); + if (curr->ifa_netmask == NULL) + goto fail; + *curr->ifa_netmask = lifrl.lifr_addr; + + /* Get the destination for a pt-pt interface */ + if (curr->ifa_flags & IFF_POINTOPOINT) { + if (ioctl(s, SIOCGLIFDSTADDR, (caddr_t)&lifrl) < 0) + goto fail; + curr->ifa_dstaddr = malloc( + sizeof (struct sockaddr_storage)); + if (curr->ifa_dstaddr == NULL) + goto fail; + *curr->ifa_dstaddr = lifrl.lifr_addr; + } else if (curr->ifa_flags & IFF_BROADCAST) { + if (ioctl(s, SIOCGLIFBRDADDR, (caddr_t)&lifrl) < 0) + goto fail; + curr->ifa_broadaddr = malloc( + sizeof (struct sockaddr_storage)); + if (curr->ifa_broadaddr == NULL) + goto fail; + *curr->ifa_broadaddr = lifrl.lifr_addr; + } + + } + free(buf); + close(sock4); + close(sock6); + return (0); +fail: + err = errno; + free(buf); + freeifaddrs(*ifap); + *ifap = NULL; + if (err == ENXIO) + goto retry; + close(sock4); + close(sock6); + errno = err; + return (-1); +} + +/* + * Do a SIOCGLIFCONF and store all the interfaces in `buf'. + */ +int +getallifs(int s, sa_family_t af, struct lifreq **lifr, int *numifs, + int64_t lifc_flags) +{ + struct lifnum lifn; + struct lifconf lifc; + size_t bufsize; + char *tmp; + caddr_t *buf = (caddr_t *)lifr; + + lifn.lifn_family = af; + lifn.lifn_flags = lifc_flags; + + *buf = NULL; +retry: + if (ioctl(s, SIOCGLIFNUM, &lifn) < 0) + goto fail; + + /* + * When calculating the buffer size needed, add a small number + * of interfaces to those we counted. We do this to capture + * the interface status of potential interfaces which may have + * been plumbed between the SIOCGLIFNUM and the SIOCGLIFCONF. + */ + bufsize = (lifn.lifn_count + 4) * sizeof (struct lifreq); + + if ((tmp = realloc(*buf, bufsize)) == NULL) + goto fail; + + *buf = tmp; + lifc.lifc_family = af; + lifc.lifc_flags = lifc_flags; + lifc.lifc_len = bufsize; + lifc.lifc_buf = *buf; + if (ioctl(s, SIOCGLIFCONF, (char *)&lifc) < 0) + goto fail; + + *numifs = lifc.lifc_len / sizeof (struct lifreq); + if (*numifs >= (lifn.lifn_count + 4)) { + /* + * If every entry was filled, there are probably + * more interfaces than (lifn.lifn_count + 4). + * Redo the ioctls SIOCGLIFNUM and SIOCGLIFCONF to + * get all the interfaces. + */ + goto retry; + } + return (0); +fail: + free(*buf); + *buf = NULL; + return (-1); +} |