summaryrefslogtreecommitdiff
path: root/usr/src/lib
diff options
context:
space:
mode:
authorSowmini Varadhan <Sowmini.Varadhan@oracle.COM>2010-07-01 17:10:52 -0400
committerSowmini Varadhan <Sowmini.Varadhan@oracle.COM>2010-07-01 17:10:52 -0400
commit550b6e4083768ca350e9e7c3a1ebbf720b23dcad (patch)
tree68629051e97e6173c4b53d2483015eeea30cacbc /usr/src/lib
parentbf7fda8965eb0f1d22b8e7bf1684b99227cd2b64 (diff)
downloadillumos-joyent-550b6e4083768ca350e9e7c3a1ebbf720b23dcad.tar.gz
PSARC 2010/166 layer-3 net properties for exclusive-IP zones
6944327 need to support address and defrouter resources for exclusive-IP zones
Diffstat (limited to 'usr/src/lib')
-rw-r--r--usr/src/lib/libdladm/common/linkprop.c16
-rw-r--r--usr/src/lib/libipadm/Makefile.com5
-rw-r--r--usr/src/lib/libipadm/common/ipadm_addr.c40
-rw-r--r--usr/src/lib/libipadm/common/ipadm_if.c13
-rw-r--r--usr/src/lib/libipadm/common/ipadm_ipmgmt.h11
-rw-r--r--usr/src/lib/libipadm/common/ipadm_ngz.c485
-rw-r--r--usr/src/lib/libipadm/common/libipadm.c27
-rw-r--r--usr/src/lib/libipadm/common/libipadm.h5
-rw-r--r--usr/src/lib/libipadm/common/libipadm_impl.h4
-rw-r--r--usr/src/lib/libipadm/common/mapfile-vers1
-rw-r--r--usr/src/lib/libzonecfg/common/libzonecfg.c49
-rw-r--r--usr/src/lib/libzonecfg/dtd/zonecfg.dtd.11
12 files changed, 633 insertions, 24 deletions
diff --git a/usr/src/lib/libdladm/common/linkprop.c b/usr/src/lib/libdladm/common/linkprop.c
index e37ab46f11..b9f38012ed 100644
--- a/usr/src/lib/libdladm/common/linkprop.c
+++ b/usr/src/lib/libdladm/common/linkprop.c
@@ -4503,11 +4503,13 @@ dladm_linkprop_is_set(dladm_handle_t handle, datalink_id_t linkid,
uint_t valcnt = DLADM_MAX_PROP_VALCNT;
int i;
dladm_status_t status = DLADM_STATUS_OK;
+ size_t bufsize;
*is_set = B_FALSE;
- if ((buf = malloc((sizeof (char *) + DLADM_PROP_VAL_MAX) *
- DLADM_MAX_PROP_VALCNT)) == NULL)
+ bufsize = (sizeof (char *) + DLADM_PROP_VAL_MAX) *
+ DLADM_MAX_PROP_VALCNT;
+ if ((buf = calloc(1, bufsize)) == NULL)
return (DLADM_STATUS_NOMEM);
propvals = (char **)(void *)buf;
@@ -4522,7 +4524,15 @@ dladm_linkprop_is_set(dladm_handle_t handle, datalink_id_t linkid,
goto done;
}
- if ((strcmp(prop_name, "pool") == 0) && (strlen(*propvals) != 0)) {
+ /*
+ * valcnt is always set to 1 by get_pool(), hence we need to check
+ * for a non-null string to see if it is set. For protection and
+ * allowed-ips, we can check either the *propval or the valcnt.
+ */
+ if ((strcmp(prop_name, "pool") == 0 ||
+ strcmp(prop_name, "protection") == 0 ||
+ strcmp(prop_name, "allowed-ips") == 0) &&
+ (strlen(*propvals) != 0)) {
*is_set = B_TRUE;
} else if ((strcmp(prop_name, "cpus") == 0) && (valcnt != 0)) {
*is_set = B_TRUE;
diff --git a/usr/src/lib/libipadm/Makefile.com b/usr/src/lib/libipadm/Makefile.com
index 60f677736e..3b6827a7fb 100644
--- a/usr/src/lib/libipadm/Makefile.com
+++ b/usr/src/lib/libipadm/Makefile.com
@@ -19,15 +19,14 @@
# CDDL HEADER END
#
#
-# Copyright 2010 Sun Microsystems, Inc. All rights reserved.
-# Use is subject to license terms.
+# Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
#
#
LIBRARY = libipadm.a
VERS = .1
OBJECTS = libipadm.o ipadm_prop.o ipadm_persist.o ipadm_addr.o ipadm_if.o \
- ipadm_ndpd.o
+ ipadm_ndpd.o ipadm_ngz.o
include ../../Makefile.lib
diff --git a/usr/src/lib/libipadm/common/ipadm_addr.c b/usr/src/lib/libipadm/common/ipadm_addr.c
index b647abf0b1..b30fe7c9cd 100644
--- a/usr/src/lib/libipadm/common/ipadm_addr.c
+++ b/usr/src/lib/libipadm/common/ipadm_addr.c
@@ -1312,7 +1312,7 @@ i_ipadm_get_zone(ipadm_handle_t iph, const void *arg,
int s;
size_t nbytes = 0;
- if (getzoneid() != GLOBAL_ZONEID) {
+ if (iph->iph_zoneid != GLOBAL_ZONEID) {
buf[0] = '\0';
return (IPADM_SUCCESS);
}
@@ -1660,7 +1660,7 @@ i_ipadm_get_default_prefixlen(struct sockaddr_storage *addr, uint32_t *plen)
return (IPADM_SUCCESS);
}
-static ipadm_status_t
+ipadm_status_t
i_ipadm_resolve_addr(const char *name, sa_family_t af,
struct sockaddr_storage *ss)
{
@@ -1935,6 +1935,9 @@ i_ipadm_setlifnum_addrobj(ipadm_handle_t iph, ipadm_addrobj_t ipaddr)
ipmgmt_retval_t rval, *rvalp;
int err;
+ if (iph->iph_flags & IPH_IPMGMTD)
+ return (IPADM_SUCCESS);
+
bzero(&larg, sizeof (larg));
larg.ia_cmd = IPMGMT_CMD_ADDROBJ_SETLIFNUM;
(void) strlcpy(larg.ia_aobjname, ipaddr->ipadm_aobjname,
@@ -2266,6 +2269,8 @@ i_ipadm_addrobj2lifname(ipadm_addrobj_t ipaddr, char *lifname, int lifnamesize)
* 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.
+ *
+ * Note that *exists will not be initialized if an error is encountered.
*/
static ipadm_status_t
i_ipadm_addr_exists_on_if(ipadm_handle_t iph, const char *ifname,
@@ -2405,6 +2410,7 @@ ipadm_create_addr(ipadm_handle_t iph, ipadm_addrobj_t addr, uint32_t flags)
boolean_t is_6to4;
struct lifreq lifr;
uint64_t ifflags;
+ boolean_t is_boot = (iph->iph_flags & IPH_IPMGMTD);
/* check for solaris.network.interface.config authorization */
if (!ipadm_check_auth())
@@ -2450,10 +2456,16 @@ ipadm_create_addr(ipadm_handle_t iph, ipadm_addrobj_t addr, uint32_t flags)
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.
+ * Skip this step if we are booting a zone (and therefore being called
+ * from ipmgmtd itself), and, for IPH_LEGACY case if the
+ * addrobj already exists.
+ *
+ * Note that the placeholder is not needed in the NGZ boot case,
+ * when zoneadmd has itself applied the "allowed-ips" property to clamp
+ * down any interface configuration, so the namespace for the interface
+ * is fully controlled by the GZ.
*/
- if (!legacy || !aobjfound) {
+ if (!is_boot && (!legacy || !aobjfound)) {
status = i_ipadm_lookupadd_addrobj(iph, addr);
if (status != IPADM_SUCCESS)
return (status);
@@ -2479,11 +2491,23 @@ ipadm_create_addr(ipadm_handle_t iph, ipadm_addrobj_t addr, uint32_t flags)
created_other_af = B_TRUE;
}
- /* Validate static addresses for IFF_POINTOPOINT interfaces. */
+ /*
+ * Some input validation based on the interface flags:
+ * 1. in non-global zones, make sure that we are not persistently
+ * creating addresses on interfaces that are acquiring
+ * address from the global zone.
+ * 2. 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;
+
+ if (iph->iph_zoneid != GLOBAL_ZONEID &&
+ (ifflags & IFF_L3PROTECT) && (flags & IPADM_OPT_PERSIST)) {
+ status = IPADM_GZ_PERM;
+ goto fail;
+ }
daf = addr->ipadm_static_dst_addr.ss_family;
if (ifflags & IFF_POINTOPOINT) {
if (is_6to4) {
@@ -2596,7 +2620,9 @@ i_ipadm_create_addr(ipadm_handle_t iph, ipadm_addrobj_t ipaddr, uint32_t flags)
boolean_t legacy = (iph->iph_flags & IPH_LEGACY);
struct ipadm_addrobj_s legacy_addr;
boolean_t default_prefixlen = B_FALSE;
+ boolean_t is_boot;
+ is_boot = ((iph->iph_flags & IPH_IPMGMTD) != 0);
af = ipaddr->ipadm_af;
sock = (af == AF_INET ? iph->iph_sock : iph->iph_sock6);
@@ -2668,7 +2694,7 @@ retry:
status = IPADM_SUCCESS;
}
- if (status == IPADM_SUCCESS) {
+ if (status == IPADM_SUCCESS && !is_boot) {
/*
* For IPH_LEGACY, we might be modifying the address on
* an address object that already exists e.g. by doing
diff --git a/usr/src/lib/libipadm/common/ipadm_if.c b/usr/src/lib/libipadm/common/ipadm_if.c
index 4591d42e9c..6d1e27dcbf 100644
--- a/usr/src/lib/libipadm/common/ipadm_if.c
+++ b/usr/src/lib/libipadm/common/ipadm_if.c
@@ -169,6 +169,8 @@ i_ipadm_active_if_info(ipadm_handle_t iph, const char *ifname,
ifp->ifi_cflags |= IFIF_IPV4;
if (lifrl.lifr_flags & IFF_IPV6)
ifp->ifi_cflags |= IFIF_IPV6;
+ if (lifrl.lifr_flags & IFF_L3PROTECT)
+ ifp->ifi_cflags |= IFIF_L3PROTECT;
}
free(buf);
return (IPADM_SUCCESS);
@@ -376,6 +378,15 @@ i_ipadm_if_pexists(ipadm_handle_t iph, const char *ifname, sa_family_t af,
ipadm_if_info_t *ifinfo;
ipadm_status_t status;
+ /*
+ * if IPH_IPMGMTD is set, we know that the caller (ipmgmtd) already
+ * knows about persistent configuration in the first place, so we
+ * just return success.
+ */
+ if (iph->iph_flags & IPH_IPMGMTD) {
+ *exists = B_FALSE;
+ return (IPADM_SUCCESS);
+ }
status = i_ipadm_persist_if_info(iph, ifname, &ifinfo);
if (status == IPADM_SUCCESS) {
*exists = ((af == AF_INET &&
@@ -688,7 +699,7 @@ i_ipadm_plumb_if(ipadm_handle_t iph, char *ifname, sa_family_t af,
* 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) {
+ if (iph->iph_zoneid == GLOBAL_ZONEID && dlstatus == DLADM_STATUS_OK) {
zoneid = ALL_ZONES;
if (zone_check_datalink(&zoneid, linkid) == 0) {
/* interface is in use by a non-global zone. */
diff --git a/usr/src/lib/libipadm/common/ipadm_ipmgmt.h b/usr/src/lib/libipadm/common/ipadm_ipmgmt.h
index 57d1023603..105e879de1 100644
--- a/usr/src/lib/libipadm/common/ipadm_ipmgmt.h
+++ b/usr/src/lib/libipadm/common/ipadm_ipmgmt.h
@@ -265,6 +265,17 @@ 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);
+/* zone related functions */
+/*
+ * callback function to persist an interface in ipmgmtd data store
+ */
+typedef void (*persist_cb_t)(char *, boolean_t, boolean_t);
+/*
+ * ipmgmtd/libipadm network initialization interface.
+ */
+extern ipadm_status_t ipadm_init_net_from_gz(ipadm_handle_t, char *,
+ persist_cb_t);
+
#ifdef __cplusplus
}
#endif
diff --git a/usr/src/lib/libipadm/common/ipadm_ngz.c b/usr/src/lib/libipadm/common/ipadm_ngz.c
new file mode 100644
index 0000000000..e5f59fe6d2
--- /dev/null
+++ b/usr/src/lib/libipadm/common/ipadm_ngz.c
@@ -0,0 +1,485 @@
+/*
+ * CDDL HEADER START
+ *
+ * The contents of this file are subject to the terms of the
+ * Common Development and Distribution License (the "License").
+ * You may not use this file except in compliance with the License.
+ *
+ * You can obtain a copy of the license at usr/src/OPENSOLARIS.LICENSE
+ * or http://www.opensolaris.org/os/licensing.
+ * See the License for the specific language governing permissions
+ * and limitations under the License.
+ *
+ * When distributing Covered Code, include this CDDL HEADER in each
+ * file and include the License file at usr/src/OPENSOLARIS.LICENSE.
+ * If applicable, add the following below this CDDL HEADER, with the
+ * fields enclosed by brackets "[]" replaced with your own identifying
+ * information: Portions Copyright [yyyy] [name of copyright owner]
+ *
+ * CDDL HEADER END
+ */
+/*
+ * Copyright (c) 2010, Oracle and/or its affiliates. All rights reserved.
+ */
+
+#include <errno.h>
+#include <fcntl.h>
+#include <priv_utils.h>
+#include <signal.h>
+#include <stdlib.h>
+#include <stdio.h>
+#include <strings.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+#include <unistd.h>
+#include <zone.h>
+#include <libipadm.h>
+#include <libdladm.h>
+#include <libdllink.h>
+#include <net/route.h>
+#include <netinet/in.h>
+#include <net/route.h>
+#include <errno.h>
+#include <inet/ip.h>
+#include <string.h>
+#include <libinetutil.h>
+#include <unistd.h>
+#include <libipadm_impl.h>
+#include <sys/brand.h>
+
+#define ROUNDUP_LONG(a) \
+ ((a) > 0 ? (1 + (((a) - 1) | (sizeof (long) - 1))) : sizeof (long))
+#define HOST_MASK 0xffffffffU
+
+typedef struct ngz_walk_data_s {
+ ipadm_handle_t ngz_iph;
+ zoneid_t ngz_zoneid;
+ char *ngz_ifname;
+ boolean_t ngz_s10c;
+ ipadm_status_t ngz_ipstatus;
+ persist_cb_t ngz_persist_if;
+} ngz_walk_data_t;
+
+/*
+ * Tell the kernel to add, delete or change a route
+ */
+static void
+i_ipadm_rtioctl4(int rtsock,
+ int action, /* RTM_DELETE, etc */
+ in_addr_t dst,
+ in_addr_t gate,
+ uint_t masklen,
+ char *ifname,
+ uint8_t metric,
+ int flags)
+{
+ static int rt_sock_seqno = 0;
+ struct {
+ struct rt_msghdr w_rtm;
+ struct sockaddr_in w_dst;
+ struct sockaddr_in w_gate;
+ uint8_t w_space[512];
+ } w;
+ struct sockaddr_in w_mask;
+ struct sockaddr_dl w_ifp;
+ uint8_t *cp;
+ long cc;
+
+again:
+ (void) memset(&w, 0, sizeof (w));
+ (void) memset(&w_mask, 0, sizeof (w_mask));
+ (void) memset(&w_ifp, 0, sizeof (w_ifp));
+ cp = w.w_space;
+ w.w_rtm.rtm_msglen = sizeof (struct rt_msghdr) +
+ 2 * ROUNDUP_LONG(sizeof (struct sockaddr_in));
+ w.w_rtm.rtm_version = RTM_VERSION;
+ w.w_rtm.rtm_type = action;
+ w.w_rtm.rtm_flags = (flags | RTF_ZONE);
+ w.w_rtm.rtm_seq = ++rt_sock_seqno;
+ w.w_rtm.rtm_addrs = RTA_DST|RTA_GATEWAY;
+ if (metric != 0 || action == RTM_CHANGE) {
+ w.w_rtm.rtm_rmx.rmx_hopcount = metric;
+ w.w_rtm.rtm_inits |= RTV_HOPCOUNT;
+ }
+ w.w_dst.sin_family = AF_INET;
+ w.w_dst.sin_addr.s_addr = dst;
+ w.w_gate.sin_family = AF_INET;
+ w.w_gate.sin_addr.s_addr = gate;
+ if (masklen == HOST_MASK) {
+ w.w_rtm.rtm_flags |= RTF_HOST;
+ } else {
+ struct sockaddr_storage m4;
+
+ w.w_rtm.rtm_addrs |= RTA_NETMASK;
+ w_mask.sin_family = AF_INET;
+ if (plen2mask(masklen, AF_INET, &m4) != 0) {
+ return;
+ }
+ w_mask.sin_addr = ((struct sockaddr_in *)&m4)->sin_addr;
+ (void) memmove(cp, &w_mask, sizeof (w_mask));
+ cp += ROUNDUP_LONG(sizeof (struct sockaddr_in));
+ w.w_rtm.rtm_msglen += ROUNDUP_LONG(sizeof (struct sockaddr_in));
+ }
+ w_ifp.sdl_family = AF_LINK;
+ w.w_rtm.rtm_addrs |= RTA_IFP;
+ w_ifp.sdl_index = if_nametoindex(ifname);
+ (void) memmove(cp, &w_ifp, sizeof (w_ifp));
+ w.w_rtm.rtm_msglen += ROUNDUP_LONG(sizeof (struct sockaddr_dl));
+
+ cc = write(rtsock, &w, w.w_rtm.rtm_msglen);
+ if (cc < 0) {
+ if (errno == ESRCH && (action == RTM_CHANGE ||
+ action == RTM_DELETE)) {
+ if (action == RTM_CHANGE) {
+ action = RTM_ADD;
+ goto again;
+ }
+ return;
+ }
+ return;
+ } else if (cc != w.w_rtm.rtm_msglen) {
+ return;
+ }
+}
+
+static void
+i_ipadm_rtioctl6(int rtsock,
+ int action, /* RTM_DELETE, etc */
+ in6_addr_t dst,
+ in6_addr_t gate,
+ uint_t prefix_length,
+ char *ifname,
+ int flags)
+{
+ static int rt_sock_seqno = 0;
+ struct {
+ struct rt_msghdr w_rtm;
+ struct sockaddr_in6 w_dst;
+ struct sockaddr_in6 w_gate;
+ uint8_t w_space[512];
+ } w;
+ struct sockaddr_in6 w_mask;
+ struct sockaddr_dl w_ifp;
+ uint8_t *cp;
+ long cc;
+
+again:
+ (void) memset(&w, 0, sizeof (w));
+ (void) memset(&w_mask, 0, sizeof (w_mask));
+ (void) memset(&w_ifp, 0, sizeof (w_ifp));
+ cp = w.w_space;
+ w.w_rtm.rtm_msglen = sizeof (struct rt_msghdr) +
+ 2 * ROUNDUP_LONG(sizeof (struct sockaddr_in6));
+ w.w_rtm.rtm_version = RTM_VERSION;
+ w.w_rtm.rtm_type = action;
+ w.w_rtm.rtm_flags = (flags | RTF_ZONE);
+ w.w_rtm.rtm_seq = ++rt_sock_seqno;
+ w.w_rtm.rtm_addrs = RTA_DST|RTA_GATEWAY;
+ w.w_dst.sin6_family = AF_INET6;
+ w.w_dst.sin6_addr = dst;
+ w.w_gate.sin6_family = AF_INET6;
+ w.w_gate.sin6_addr = gate;
+ if (prefix_length == IPV6_ABITS) {
+ w.w_rtm.rtm_flags |= RTF_HOST;
+ } else {
+ struct sockaddr_storage m6;
+
+ w.w_rtm.rtm_addrs |= RTA_NETMASK;
+ w_mask.sin6_family = AF_INET6;
+ if (plen2mask(prefix_length, AF_INET6, &m6) != 0) {
+ return;
+ }
+ w_mask.sin6_addr = ((struct sockaddr_in6 *)&m6)->sin6_addr;
+ (void) memmove(cp, &w_mask, sizeof (w_mask));
+ cp += ROUNDUP_LONG(sizeof (struct sockaddr_in6));
+ w.w_rtm.rtm_msglen +=
+ ROUNDUP_LONG(sizeof (struct sockaddr_in6));
+ }
+ w_ifp.sdl_family = AF_LINK;
+ w.w_rtm.rtm_addrs |= RTA_IFP;
+ w_ifp.sdl_index = if_nametoindex(ifname);
+ (void) memmove(cp, &w_ifp, sizeof (w_ifp));
+ w.w_rtm.rtm_msglen += ROUNDUP_LONG(sizeof (struct sockaddr_dl));
+
+ cc = write(rtsock, &w, w.w_rtm.rtm_msglen);
+ if (cc < 0) {
+ if (errno == ESRCH && (action == RTM_CHANGE ||
+ action == RTM_DELETE)) {
+ if (action == RTM_CHANGE) {
+ action = RTM_ADD;
+ goto again;
+ }
+ return;
+ }
+ return;
+ } else if (cc != w.w_rtm.rtm_msglen) {
+ return;
+ }
+}
+
+/*
+ * Return TRUE if running in a Solaris 10 Container.
+ */
+static boolean_t
+i_ipadm_zone_is_s10c(zoneid_t zoneid)
+{
+ char brand[MAXNAMELEN];
+
+ if (zone_getattr(zoneid, ZONE_ATTR_BRAND, brand, sizeof (brand)) < 0)
+ return (B_FALSE);
+ return (strcmp(brand, NATIVE_BRAND_NAME) != 0);
+}
+
+/*
+ * Configure addresses on link. `buf' is a string of comma-separated
+ * IP addresses.
+ */
+static ipadm_status_t
+i_ipadm_ngz_addr(ipadm_handle_t iph, char *link, char *buf)
+{
+ ipadm_status_t ipstatus;
+ ipadm_addrobj_t ipaddr;
+ char *cp;
+
+ for (cp = strtok(buf, ","); cp != NULL; cp = strtok(NULL, ",")) {
+ ipstatus = ipadm_create_addrobj(IPADM_ADDR_STATIC, link,
+ &ipaddr);
+ if (ipstatus != IPADM_SUCCESS)
+ return (ipstatus);
+ /*
+ * ipadm_set_addr does the appropriate name resolution and
+ * sets up the ipadm_static_addr field.
+ */
+ ipstatus = ipadm_set_addr(ipaddr, cp, AF_UNSPEC);
+ if (ipstatus != IPADM_SUCCESS) {
+ ipadm_destroy_addrobj(ipaddr);
+ return (ipstatus);
+ }
+
+ ipstatus = ipadm_create_addr(iph, ipaddr,
+ (IPADM_OPT_ACTIVE | IPADM_OPT_UP));
+ if (ipstatus != IPADM_SUCCESS) {
+ ipadm_destroy_addrobj(ipaddr);
+ return (ipstatus);
+ }
+ ipadm_destroy_addrobj(ipaddr);
+ }
+ return (IPADM_SUCCESS);
+}
+
+/*
+ * The (*persist_if)() will set up persistent information for the interface,
+ * based on what interface families are required, so just resolve the
+ * address and inform the callback about the linkname, and required address
+ * families.
+ */
+static ipadm_status_t
+i_ipadm_ngz_persist_if(char *link, char *buf,
+ void (*ngz_persist_if)(char *, boolean_t, boolean_t))
+{
+ char *cp, *slashp, addr[INET6_ADDRSTRLEN];
+ ipadm_status_t ipstatus;
+ struct sockaddr_storage ss;
+ boolean_t v4 = B_FALSE;
+ boolean_t v6 = B_FALSE;
+
+ for (cp = strtok(buf, ","); cp != NULL; cp = strtok(NULL, ",")) {
+ /* remove the /<masklen> that's always added by zoneadmd */
+ slashp = strchr(cp, '/');
+ (void) strlcpy(addr, cp, (slashp - cp + 1));
+
+ /* resolve the address to find the family */
+ bzero(&ss, sizeof (ss));
+ ipstatus = i_ipadm_resolve_addr(addr, AF_UNSPEC, &ss);
+ if (ipstatus != IPADM_SUCCESS)
+ return (ipstatus);
+ switch (ss.ss_family) {
+ case AF_INET:
+ v4 = B_TRUE;
+ break;
+ case AF_INET6:
+ v6 = B_TRUE;
+ break;
+ default:
+ return (IPADM_BAD_ADDR);
+ }
+ }
+ (*ngz_persist_if)(link, v4, v6);
+ return (IPADM_SUCCESS);
+}
+
+static void
+i_ipadm_create_ngz_route(int rtsock, char *link, uint8_t *buf, size_t buflen)
+{
+ struct in6_addr defrouter;
+ boolean_t isv6;
+ struct in_addr gw4;
+ uint8_t *cp;
+ const in6_addr_t ipv6_all_zeros = { 0, 0, 0, 0 };
+
+ if (rtsock == -1)
+ return;
+
+ for (cp = buf; cp < buf + buflen; cp += sizeof (defrouter)) {
+ bcopy(cp, &defrouter, sizeof (defrouter));
+ if (IN6_IS_ADDR_UNSPECIFIED(&defrouter))
+ break;
+ isv6 = !IN6_IS_ADDR_V4MAPPED(&defrouter);
+ if (isv6) {
+ i_ipadm_rtioctl6(rtsock, RTM_ADD, ipv6_all_zeros,
+ defrouter, 0, link, RTF_GATEWAY);
+ } else {
+ IN6_V4MAPPED_TO_INADDR(&defrouter, &gw4);
+ i_ipadm_rtioctl4(rtsock, RTM_ADD, INADDR_ANY,
+ gw4.s_addr, 0, link, 0, RTF_GATEWAY);
+ }
+ }
+}
+
+/*
+ * Wrapper function to zone_getattr() for retrieving from-gz attributes that
+ * were made availabe for exclusive IP non-global zones by zoneadmd from teh
+ * global zone.
+ */
+static ipadm_status_t
+i_ipadm_zone_get_network(zoneid_t zoneid, datalink_id_t linkid, int type,
+ void *buf, size_t *bufsize)
+{
+ zone_net_data_t *zndata;
+
+ zndata = calloc(1, sizeof (*zndata) + *bufsize);
+ if (zndata == NULL)
+ return (IPADM_NO_MEMORY);
+ zndata->zn_type = type;
+ zndata->zn_linkid = linkid;
+ zndata->zn_len = *bufsize;
+
+ if (zone_getattr(zoneid, ZONE_ATTR_NETWORK, zndata,
+ sizeof (*zndata) + *bufsize) < 0) {
+ return (ipadm_errno2status(errno));
+ }
+ *bufsize = zndata->zn_len;
+ bcopy(zndata->zn_val, buf, *bufsize);
+ return (IPADM_SUCCESS);
+}
+
+/*
+ * Callback function that configures a single datalink in a non-global zone.
+ */
+static int
+i_ipadm_zone_network_attr(dladm_handle_t dh, datalink_id_t linkid, void *arg)
+{
+ ngz_walk_data_t *nwd = arg;
+ zoneid_t zoneid = nwd->ngz_zoneid;
+ uint8_t buf[PIPE_BUF];
+ dladm_status_t dlstatus;
+ ipadm_status_t ipstatus;
+ char link[MAXLINKNAMELEN];
+ ipadm_handle_t iph = nwd->ngz_iph;
+ int rtsock = iph->iph_rtsock;
+ char *ifname = nwd->ngz_ifname;
+ boolean_t s10c = nwd->ngz_s10c;
+ boolean_t is_ipmgmtd = (iph->iph_flags & IPH_IPMGMTD);
+ size_t bufsize = sizeof (buf);
+
+ bzero(buf, bufsize);
+ ipstatus = i_ipadm_zone_get_network(zoneid, linkid,
+ ZONE_NETWORK_ADDRESS, buf, &bufsize);
+ if (ipstatus != IPADM_SUCCESS)
+ goto fail;
+
+ dlstatus = dladm_datalink_id2info(dh, linkid, NULL, NULL,
+ NULL, link, sizeof (link));
+ if (dlstatus != DLADM_STATUS_OK)
+ return (DLADM_WALK_CONTINUE);
+
+ /*
+ * if ifname has been specified, then skip interfaces that don't match
+ */
+ if (ifname != NULL && strcmp(ifname, link) != 0)
+ return (DLADM_WALK_CONTINUE);
+
+ /*
+ * Plumb the interface and configure addresses on for S10 Containers.
+ * We need to always do this for S10C because ipadm persistent
+ * configuration is not available in S10C. For ipkg zones,
+ * we skip the actual plumbing/configuration, but will call the
+ * (*ngz_persist_if)() callback to create the persistent state for the
+ * interface. The interface will be configured in ipkg zones when
+ * ipadm_enable_if() is invoked to restore persistent configuration.
+ */
+ if (is_ipmgmtd && !s10c) {
+ (void) i_ipadm_ngz_persist_if(link, (char *)buf,
+ nwd->ngz_persist_if);
+ return (DLADM_WALK_CONTINUE);
+ }
+ ipstatus = i_ipadm_ngz_addr(iph, link, (char *)buf);
+ if (ipstatus != IPADM_SUCCESS)
+ goto fail;
+
+ /* apply any default router information. */
+ bufsize = sizeof (buf);
+ bzero(buf, bufsize);
+ ipstatus = i_ipadm_zone_get_network(zoneid, linkid,
+ ZONE_NETWORK_DEFROUTER, buf, &bufsize);
+ if (ipstatus != IPADM_SUCCESS)
+ goto fail;
+
+ i_ipadm_create_ngz_route(rtsock, link, buf, bufsize);
+
+ return (DLADM_WALK_CONTINUE);
+fail:
+ if (ifname != NULL) {
+ nwd->ngz_ipstatus = ipstatus;
+ return (DLADM_WALK_TERMINATE);
+ }
+ return (DLADM_WALK_CONTINUE);
+}
+
+/*
+ * ipmgmt_net_from_gz_init() initializes exclusive-IP stack non-global zones by
+ * extracting configuration that has been saved in the kernel and applying
+ * that information to the appropriate datalinks for the zone. If an ifname
+ * argument is passed in, only the selected IP interface corresponding to
+ * datalink will be initialized, otherwise all datalinks will be plumbed for IP
+ * and IP address and route information will be configured.
+ */
+ipadm_status_t
+ipadm_init_net_from_gz(ipadm_handle_t iph, char *ifname,
+ void (*persist_if)(char *, boolean_t, boolean_t))
+{
+ ngz_walk_data_t nwd;
+ uint64_t flags;
+ dladm_handle_t dlh = iph->iph_dlh;
+ datalink_id_t linkid;
+
+ if (iph->iph_zoneid == GLOBAL_ZONEID)
+ return (IPADM_NOTSUP);
+
+ if (ifname != NULL &&
+ i_ipadm_get_flags(iph, ifname, AF_INET, &flags) != IPADM_SUCCESS &&
+ i_ipadm_get_flags(iph, ifname, AF_INET6, &flags) != IPADM_SUCCESS)
+ return (IPADM_ENXIO);
+
+ if (ifname != NULL && !(flags & IFF_L3PROTECT))
+ return (IPADM_SUCCESS); /* nothing to initialize */
+
+ nwd.ngz_iph = iph;
+ nwd.ngz_zoneid = iph->iph_zoneid;
+ nwd.ngz_ifname = ifname;
+ nwd.ngz_persist_if = persist_if;
+ nwd.ngz_s10c = i_ipadm_zone_is_s10c(iph->iph_zoneid);
+ nwd.ngz_ipstatus = IPADM_SUCCESS;
+ if (ifname != NULL) {
+ if (dladm_name2info(dlh, ifname, &linkid, NULL, NULL,
+ NULL) != DLADM_STATUS_OK) {
+ return (IPADM_ENXIO);
+ }
+ (void) i_ipadm_zone_network_attr(dlh, linkid, &nwd);
+ } else {
+ (void) dladm_walk_datalink_id(i_ipadm_zone_network_attr, dlh,
+ &nwd, DATALINK_CLASS_ALL, DATALINK_ANY_MEDIATYPE,
+ DLADM_OPT_PERSIST);
+ }
+ return (nwd.ngz_ipstatus);
+}
diff --git a/usr/src/lib/libipadm/common/libipadm.c b/usr/src/lib/libipadm/common/libipadm.c
index 4103ebbd85..21aeab72ba 100644
--- a/usr/src/lib/libipadm/common/libipadm.c
+++ b/usr/src/lib/libipadm/common/libipadm.c
@@ -97,7 +97,8 @@ static struct ipadm_error_info {
{ 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" }
+ { IPADM_EBADE, "Invalid data exchange with daemon" },
+ { IPADM_GZ_PERM, "Operation not permitted on from-gz interface"}
};
#define IPADM_NUM_ERRORS (sizeof (ipadm_errors) / sizeof (*ipadm_errors))
@@ -186,7 +187,7 @@ ipadm_open(ipadm_handle_t *handle, uint32_t flags)
return (IPADM_INVALID_ARG);
*handle = NULL;
- if (flags & ~(IPH_VRRP|IPH_LEGACY|IPH_INIT))
+ if (flags & ~(IPH_VRRP|IPH_LEGACY|IPH_INIT|IPH_IPMGMTD))
return (IPADM_INVALID_ARG);
if ((iph = calloc(1, sizeof (struct ipadm_handle))) == NULL)
@@ -194,6 +195,7 @@ ipadm_open(ipadm_handle_t *handle, uint32_t flags)
iph->iph_sock = -1;
iph->iph_sock6 = -1;
iph->iph_door_fd = -1;
+ iph->iph_rtsock = -1;
iph->iph_flags = flags;
(void) pthread_mutex_init(&iph->iph_lock, NULL);
@@ -213,6 +215,7 @@ ipadm_open(ipadm_handle_t *handle, uint32_t flags)
* dladm_open() for such zones.
*/
zoneid = getzoneid();
+ iph->iph_zoneid = zoneid;
if (zoneid != GLOBAL_ZONEID) {
if (zone_getattr(zoneid, ZONE_ATTR_FLAGS, &zflags,
sizeof (zflags)) < 0) {
@@ -224,6 +227,14 @@ ipadm_open(ipadm_handle_t *handle, uint32_t flags)
ipadm_close(iph);
return (IPADM_DLADM_FAILURE);
}
+ if (zoneid != GLOBAL_ZONEID) {
+ iph->iph_rtsock = socket(PF_ROUTE, SOCK_RAW, 0);
+ /*
+ * Failure to open rtsock is ignored as this is
+ * only used in non-global zones to initialize
+ * routing socket information.
+ */
+ }
} else {
assert(zoneid != GLOBAL_ZONEID);
iph->iph_dlh = NULL;
@@ -256,6 +267,8 @@ ipadm_close(ipadm_handle_t iph)
(void) close(iph->iph_sock);
if (iph->iph_sock6 != -1)
(void) close(iph->iph_sock6);
+ if (iph->iph_rtsock != -1)
+ (void) close(iph->iph_rtsock);
if (iph->iph_door_fd != -1)
(void) close(iph->iph_door_fd);
dladm_close(iph->iph_dlh);
@@ -494,7 +507,7 @@ i_ipadm_is_6to4(ipadm_handle_t iph, char *ifname)
datalink_id_t linkid;
if (iph->iph_dlh == NULL) {
- assert(getzoneid() != GLOBAL_ZONEID);
+ assert(iph->iph_zoneid != GLOBAL_ZONEID);
return (B_FALSE);
}
dlstatus = dladm_name2info(iph->iph_dlh, ifname, &linkid, NULL,
@@ -682,6 +695,8 @@ i_ipadm_init_ifobj(ipadm_handle_t iph, const char *ifname, nvlist_t *ifnvl)
ipadm_status_t ret_status = IPADM_SUCCESS;
char newifname[LIFNAMSIZ];
char *aobjstr;
+ sa_family_t af = AF_UNSPEC;
+ boolean_t is_ngz = (iph->iph_zoneid != GLOBAL_ZONEID);
(void) strlcpy(newifname, ifname, sizeof (newifname));
/*
@@ -705,6 +720,9 @@ i_ipadm_init_ifobj(ipadm_handle_t iph, const char *ifname, nvlist_t *ifnvl)
*/
if (status == IPADM_IF_EXISTS)
status = IPADM_SUCCESS;
+
+ if (is_ngz)
+ af = atoi(afstr);
} else if (nvlist_lookup_string(nvl, IPADM_NVP_AOBJNAME,
&aobjstr) == 0) {
/*
@@ -736,6 +754,9 @@ i_ipadm_init_ifobj(ipadm_handle_t iph, const char *ifname, nvlist_t *ifnvl)
if (status != IPADM_SUCCESS)
return (status);
}
+
+ if (is_ngz && af != AF_UNSPEC)
+ ret_status = ipadm_init_net_from_gz(iph, newifname, NULL);
return (ret_status);
}
diff --git a/usr/src/lib/libipadm/common/libipadm.h b/usr/src/lib/libipadm/common/libipadm.h
index 32fb89e2e8..8ecc8db706 100644
--- a/usr/src/lib/libipadm/common/libipadm.h
+++ b/usr/src/lib/libipadm/common/libipadm.h
@@ -91,7 +91,8 @@ typedef enum {
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_EBADE, /* Invalid data exchange with ipmgmtd */
+ IPADM_GZ_PERM /* Operation not permitted on from-gz intf */
} ipadm_status_t;
/*
@@ -171,6 +172,7 @@ 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 */
+#define IPH_IPMGMTD 0x00000004 /* Caller is ipmgmtd itself */
/* opaque address object structure */
typedef struct ipadm_addrobj_s *ipadm_addrobj_t;
@@ -204,6 +206,7 @@ typedef struct ipadm_if_info_s {
#define IFIF_NOACCEPT 0x00000100
#define IFIF_IPV4 0x00000200
#define IFIF_IPV6 0x00000400
+#define IFIF_L3PROTECT 0x00000800
/* ipadm_addr_info_t state */
typedef enum {
diff --git a/usr/src/lib/libipadm/common/libipadm_impl.h b/usr/src/lib/libipadm/common/libipadm_impl.h
index ff953dfd1f..b5604307b2 100644
--- a/usr/src/lib/libipadm/common/libipadm_impl.h
+++ b/usr/src/lib/libipadm/common/libipadm_impl.h
@@ -55,9 +55,11 @@ struct ipadm_handle {
int iph_sock; /* socket to interface */
int iph_sock6; /* socket to interface */
int iph_door_fd; /* door descriptor to ipmgmtd */
+ int iph_rtsock; /* routing socket */
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 */
+ zoneid_t iph_zoneid; /* zoneid where handle was opened */
};
/*
@@ -210,6 +212,8 @@ 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 *);
+extern ipadm_status_t i_ipadm_resolve_addr(const char *, sa_family_t,
+ struct sockaddr_storage *);
/* ipadm_if.c */
extern ipadm_status_t i_ipadm_create_if(ipadm_handle_t, char *, sa_family_t,
diff --git a/usr/src/lib/libipadm/common/mapfile-vers b/usr/src/lib/libipadm/common/mapfile-vers
index 4819963afb..e893f5ae37 100644
--- a/usr/src/lib/libipadm/common/mapfile-vers
+++ b/usr/src/lib/libipadm/common/mapfile-vers
@@ -67,6 +67,7 @@ SYMBOL_VERSION SUNWprivate_1.1 {
ipadm_if_info;
ipadm_if_move;
ipadm_init_prop;
+ ipadm_init_net_from_gz;
ipadm_ndpd_read;
ipadm_ndpd_write;
ipadm_nvlist2str;
diff --git a/usr/src/lib/libzonecfg/common/libzonecfg.c b/usr/src/lib/libzonecfg/common/libzonecfg.c
index b927b6b954..c9da45b76a 100644
--- a/usr/src/lib/libzonecfg/common/libzonecfg.c
+++ b/usr/src/lib/libzonecfg/common/libzonecfg.c
@@ -99,6 +99,7 @@
#define DTD_ATTR_ACTION (const xmlChar *) "action"
#define DTD_ATTR_ADDRESS (const xmlChar *) "address"
+#define DTD_ATTR_ALLOWED_ADDRESS (const xmlChar *) "allowed-address"
#define DTD_ATTR_AUTOBOOT (const xmlChar *) "autoboot"
#define DTD_ATTR_IPTYPE (const xmlChar *) "ip-type"
#define DTD_ATTR_DEFROUTER (const xmlChar *) "defrouter"
@@ -2090,6 +2091,8 @@ zonecfg_lookup_nwif(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
size_t addrspec; /* nonzero if tabptr has IP addr */
size_t physspec; /* nonzero if tabptr has interface */
size_t defrouterspec; /* nonzero if tabptr has def. router */
+ size_t allowed_addrspec;
+ zone_iptype_t iptype;
if (tabptr == NULL)
return (Z_INVAL);
@@ -2104,12 +2107,18 @@ zonecfg_lookup_nwif(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
addrspec = strlen(tabptr->zone_nwif_address);
physspec = strlen(tabptr->zone_nwif_physical);
defrouterspec = strlen(tabptr->zone_nwif_defrouter);
- if (addrspec == 0 && physspec == 0 && defrouterspec == 0)
+ allowed_addrspec = strlen(tabptr->zone_nwif_allowed_address);
+ if (addrspec != 0 && allowed_addrspec != 0)
+ return (Z_INVAL); /* can't specify both */
+ if (addrspec == 0 && physspec == 0 && defrouterspec == 0 &&
+ allowed_addrspec == 0)
return (Z_INSUFFICIENT_SPEC);
if ((err = operation_prep(handle)) != Z_OK)
return (err);
+ if ((err = zonecfg_get_iptype(handle, &iptype)) != Z_OK)
+ return (err);
/*
* Iterate over the configuration's elements and look for net elements
* that match the query.
@@ -2129,11 +2138,18 @@ zonecfg_lookup_nwif(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
physical, sizeof (physical)) != Z_OK ||
strcmp(tabptr->zone_nwif_physical, physical) != 0))
continue;
- if (addrspec != 0 && (fetchprop(cur, DTD_ATTR_ADDRESS, address,
+ if (iptype == ZS_SHARED && addrspec != 0 &&
+ (fetchprop(cur, DTD_ATTR_ADDRESS, address,
sizeof (address)) != Z_OK ||
!zonecfg_same_net_address(tabptr->zone_nwif_address,
address)))
continue;
+ if (iptype == ZS_EXCLUSIVE && allowed_addrspec != 0 &&
+ (fetchprop(cur, DTD_ATTR_ALLOWED_ADDRESS, address,
+ sizeof (address)) != Z_OK ||
+ !zonecfg_same_net_address(tabptr->zone_nwif_allowed_address,
+ address)))
+ continue;
if (defrouterspec != 0 && (fetchprop(cur, DTD_ATTR_DEFROUTER,
address, sizeof (address)) != Z_OK ||
!zonecfg_same_net_address(tabptr->zone_nwif_defrouter,
@@ -2158,10 +2174,17 @@ zonecfg_lookup_nwif(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
sizeof (tabptr->zone_nwif_physical))) != Z_OK)
return (err);
- if ((err = fetchprop(cur, DTD_ATTR_ADDRESS, tabptr->zone_nwif_address,
+ if (iptype == ZS_SHARED &&
+ (err = fetchprop(cur, DTD_ATTR_ADDRESS, tabptr->zone_nwif_address,
sizeof (tabptr->zone_nwif_address))) != Z_OK)
return (err);
+ if (iptype == ZS_EXCLUSIVE &&
+ (err = fetchprop(cur, DTD_ATTR_ALLOWED_ADDRESS,
+ tabptr->zone_nwif_allowed_address,
+ sizeof (tabptr->zone_nwif_allowed_address))) != Z_OK)
+ return (err);
+
if ((err = fetchprop(cur, DTD_ATTR_DEFROUTER,
tabptr->zone_nwif_defrouter,
sizeof (tabptr->zone_nwif_defrouter))) != Z_OK)
@@ -2177,9 +2200,14 @@ zonecfg_add_nwif_core(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
int err;
newnode = xmlNewTextChild(cur, NULL, DTD_ELEM_NET, NULL);
- if ((err = newprop(newnode, DTD_ATTR_ADDRESS,
+ if (strlen(tabptr->zone_nwif_address) > 0 &&
+ (err = newprop(newnode, DTD_ATTR_ADDRESS,
tabptr->zone_nwif_address)) != Z_OK)
return (err);
+ if (strlen(tabptr->zone_nwif_allowed_address) > 0 &&
+ (err = newprop(newnode, DTD_ATTR_ALLOWED_ADDRESS,
+ tabptr->zone_nwif_allowed_address)) != Z_OK)
+ return (err);
if ((err = newprop(newnode, DTD_ATTR_PHYSICAL,
tabptr->zone_nwif_physical)) != Z_OK)
return (err);
@@ -2215,7 +2243,7 @@ static int
zonecfg_delete_nwif_core(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
{
xmlNodePtr cur = handle->zone_dh_cur;
- boolean_t addr_match, phys_match;
+ boolean_t addr_match, phys_match, allowed_addr_match;
for (cur = cur->xmlChildrenNode; cur != NULL; cur = cur->next) {
if (xmlStrcmp(cur->name, DTD_ELEM_NET))
@@ -2223,10 +2251,12 @@ zonecfg_delete_nwif_core(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
addr_match = match_prop(cur, DTD_ATTR_ADDRESS,
tabptr->zone_nwif_address);
+ allowed_addr_match = match_prop(cur, DTD_ATTR_ALLOWED_ADDRESS,
+ tabptr->zone_nwif_allowed_address);
phys_match = match_prop(cur, DTD_ATTR_PHYSICAL,
tabptr->zone_nwif_physical);
- if (addr_match && phys_match) {
+ if ((addr_match || allowed_addr_match) && phys_match) {
xmlUnlinkNode(cur);
xmlFreeNode(cur);
return (Z_OK);
@@ -4734,6 +4764,13 @@ zonecfg_getnwifent(zone_dochandle_t handle, struct zone_nwiftab *tabptr)
return (err);
}
+ if ((err = fetchprop(cur, DTD_ATTR_ALLOWED_ADDRESS,
+ tabptr->zone_nwif_allowed_address,
+ sizeof (tabptr->zone_nwif_allowed_address))) != Z_OK) {
+ handle->zone_dh_cur = handle->zone_dh_top;
+ return (err);
+ }
+
if ((err = fetchprop(cur, DTD_ATTR_PHYSICAL, tabptr->zone_nwif_physical,
sizeof (tabptr->zone_nwif_physical))) != Z_OK) {
handle->zone_dh_cur = handle->zone_dh_top;
diff --git a/usr/src/lib/libzonecfg/dtd/zonecfg.dtd.1 b/usr/src/lib/libzonecfg/dtd/zonecfg.dtd.1
index d1857cd7c5..d94bb09c5f 100644
--- a/usr/src/lib/libzonecfg/dtd/zonecfg.dtd.1
+++ b/usr/src/lib/libzonecfg/dtd/zonecfg.dtd.1
@@ -49,6 +49,7 @@
<!ELEMENT network EMPTY>
<!ATTLIST network address CDATA ""
+ allowed-address CDATA ""
defrouter CDATA ""
physical CDATA #REQUIRED>