summaryrefslogtreecommitdiff
path: root/usr/src/cmd/zoneadmd/vplat.c
diff options
context:
space:
mode:
Diffstat (limited to 'usr/src/cmd/zoneadmd/vplat.c')
-rw-r--r--usr/src/cmd/zoneadmd/vplat.c487
1 files changed, 483 insertions, 4 deletions
diff --git a/usr/src/cmd/zoneadmd/vplat.c b/usr/src/cmd/zoneadmd/vplat.c
index 998fc6a750..b4cc20951f 100644
--- a/usr/src/cmd/zoneadmd/vplat.c
+++ b/usr/src/cmd/zoneadmd/vplat.c
@@ -119,6 +119,7 @@
#include <tsol/label.h>
#include <libtsnet.h>
#include <sys/priv.h>
+#include <libinetutil.h>
#define V4_ADDR_LEN 32
#define V6_ADDR_LEN 128
@@ -159,11 +160,29 @@ extern int getnetmaskbyaddr(struct in_addr, struct in_addr *);
extern char query_hook[];
/*
+ * For each "net" resource configured in zonecfg, we track a zone_addr_list_t
+ * node in a linked list that is sorted by linkid. The list is constructed as
+ * the xml configuration file is parsed, and the information
+ * contained in each node is added to the kernel before the zone is
+ * booted, to be retrieved and applied from within the exclusive-IP NGZ
+ * on boot.
+ */
+typedef struct zone_addr_list {
+ struct zone_addr_list *za_next;
+ datalink_id_t za_linkid; /* datalink_id_t of interface */
+ struct zone_nwiftab za_nwiftab; /* address, defrouter properties */
+} zone_addr_list_t;
+
+/*
* An optimization for build_mnttable: reallocate (and potentially copy the
* data) only once every N times through the loop.
*/
#define MNTTAB_HUNK 32
+/* some handy macros */
+#define SIN(s) ((struct sockaddr_in *)s)
+#define SIN6(s) ((struct sockaddr_in6 *)s)
+
/*
* Private autofs system call
*/
@@ -2502,13 +2521,369 @@ add_datalink(zlog_t *zlogp, char *zone_name, datalink_id_t linkid, char *dlname)
return (0);
}
+static boolean_t
+sockaddr_to_str(sa_family_t af, const struct sockaddr *sockaddr,
+ char *straddr, size_t len)
+{
+ struct sockaddr_in *sin;
+ struct sockaddr_in6 *sin6;
+ const char *str = NULL;
+
+ if (af == AF_INET) {
+ /* LINTED E_BAD_PTR_CAST_ALIGN */
+ sin = SIN(sockaddr);
+ str = inet_ntop(AF_INET, (void *)&sin->sin_addr, straddr, len);
+ } else if (af == AF_INET6) {
+ /* LINTED E_BAD_PTR_CAST_ALIGN */
+ sin6 = SIN6(sockaddr);
+ str = inet_ntop(AF_INET6, (void *)&sin6->sin6_addr, straddr,
+ len);
+ }
+
+ return (str != NULL);
+}
+
+static int
+ipv4_prefixlen(struct sockaddr_in *sin)
+{
+ struct sockaddr_in *m;
+ struct sockaddr_storage mask;
+
+ m = SIN(&mask);
+ m->sin_family = AF_INET;
+ if (getnetmaskbyaddr(sin->sin_addr, &m->sin_addr) == 0) {
+ return (mask2plen(&mask));
+ } else if (IN_CLASSA(htonl(sin->sin_addr.s_addr))) {
+ return (8);
+ } else if (IN_CLASSB(ntohl(sin->sin_addr.s_addr))) {
+ return (16);
+ } else if (IN_CLASSC(ntohl(sin->sin_addr.s_addr))) {
+ return (24);
+ }
+ return (0);
+}
+
+static int
+zone_setattr_network(int type, zoneid_t zoneid, datalink_id_t linkid,
+ void *buf, size_t bufsize)
+{
+ zone_net_data_t *zndata;
+ size_t znsize;
+ int err;
+
+ znsize = sizeof (*zndata) + bufsize;
+ zndata = calloc(1, znsize);
+ if (zndata == NULL)
+ return (ENOMEM);
+ zndata->zn_type = type;
+ zndata->zn_len = bufsize;
+ zndata->zn_linkid = linkid;
+ bcopy(buf, zndata->zn_val, zndata->zn_len);
+ err = zone_setattr(zoneid, ZONE_ATTR_NETWORK, zndata, znsize);
+ free(zndata);
+ return (err);
+}
+
+static int
+add_net_for_linkid(zlog_t *zlogp, zoneid_t zoneid, zone_addr_list_t *start)
+{
+ struct lifreq lifr;
+ char **astr, *address;
+ dladm_status_t dlstatus;
+ char *ip_nospoof = "ip-nospoof";
+ int nnet, naddr, err = 0, j;
+ size_t zlen, cpleft;
+ zone_addr_list_t *ptr, *end;
+ char tmp[INET6_ADDRSTRLEN], *maskstr;
+ char *zaddr, *cp;
+ struct in6_addr *routes = NULL;
+ boolean_t is_set;
+ datalink_id_t linkid;
+
+ assert(start != NULL);
+ naddr = 0; /* number of addresses */
+ nnet = 0; /* number of net resources */
+ linkid = start->za_linkid;
+ for (ptr = start; ptr != NULL && ptr->za_linkid == linkid;
+ ptr = ptr->za_next) {
+ nnet++;
+ }
+ end = ptr;
+ zlen = nnet * (INET6_ADDRSTRLEN + 1);
+ astr = calloc(1, nnet * sizeof (uintptr_t));
+ zaddr = calloc(1, zlen);
+ if (astr == NULL || zaddr == NULL) {
+ err = ENOMEM;
+ goto done;
+ }
+ cp = zaddr;
+ cpleft = zlen;
+ j = 0;
+ for (ptr = start; ptr != end; ptr = ptr->za_next) {
+ address = ptr->za_nwiftab.zone_nwif_allowed_address;
+ if (address[0] == '\0')
+ continue;
+ (void) snprintf(tmp, sizeof (tmp), "%s", address);
+ /*
+ * Validate the data. zonecfg_valid_net_address() clobbers
+ * the /<mask> in the address string.
+ */
+ if (zonecfg_valid_net_address(address, &lifr) != Z_OK) {
+ zerror(zlogp, B_FALSE, "invalid address [%s]\n",
+ address);
+ err = EINVAL;
+ goto done;
+ }
+ /*
+ * convert any hostnames to numeric address strings.
+ */
+ if (!sockaddr_to_str(lifr.lifr_addr.ss_family,
+ (const struct sockaddr *)&lifr.lifr_addr, cp, cpleft)) {
+ err = EINVAL;
+ goto done;
+ }
+ /*
+ * make a copy of the numeric string for the data needed
+ * by the "allowed-ips" datalink property.
+ */
+ astr[j] = strdup(cp);
+ if (astr[j] == NULL) {
+ err = ENOMEM;
+ goto done;
+ }
+ j++;
+ /*
+ * compute the default netmask from the address, if necessary
+ */
+ if ((maskstr = strchr(tmp, '/')) == NULL) {
+ int prefixlen;
+
+ if (lifr.lifr_addr.ss_family == AF_INET) {
+ prefixlen = ipv4_prefixlen(
+ SIN(&lifr.lifr_addr));
+ } else {
+ struct sockaddr_in6 *sin6;
+
+ sin6 = SIN6(&lifr.lifr_addr);
+ if (IN6_IS_ADDR_LINKLOCAL(&sin6->sin6_addr))
+ prefixlen = 10;
+ else
+ prefixlen = 64;
+ }
+ (void) snprintf(tmp, sizeof (tmp), "%d", prefixlen);
+ maskstr = tmp;
+ } else {
+ maskstr++;
+ }
+ /* append the "/<netmask>" */
+ (void) strlcat(cp, "/", cpleft);
+ (void) strlcat(cp, maskstr, cpleft);
+ (void) strlcat(cp, ",", cpleft);
+ cp += strnlen(cp, zlen);
+ cpleft = &zaddr[INET6_ADDRSTRLEN] - cp;
+ }
+ naddr = j; /* the actual number of addresses in the net resource */
+ assert(naddr <= nnet);
+
+ /*
+ * zonecfg has already verified that the defrouter property can only
+ * be set if there is at least one address defined for the net resource.
+ * If j is 0, there are no addresses defined, and therefore no routers
+ * to configure, and we are done at that point.
+ */
+ if (j == 0)
+ goto done;
+
+ /* over-write last ',' with '\0' */
+ zaddr[strnlen(zaddr, zlen) + 1] = '\0';
+
+ /*
+ * First make sure L3 protection is not already set on the link.
+ */
+ dlstatus = dladm_linkprop_is_set(dld_handle, linkid, DLADM_OPT_ACTIVE,
+ "protection", &is_set);
+ if (dlstatus != DLADM_STATUS_OK) {
+ err = EINVAL;
+ zerror(zlogp, B_FALSE, "unable to check if protection is set");
+ goto done;
+ }
+ if (is_set) {
+ err = EINVAL;
+ zerror(zlogp, B_FALSE, "Protection is already set");
+ goto done;
+ }
+ dlstatus = dladm_linkprop_is_set(dld_handle, linkid, DLADM_OPT_ACTIVE,
+ "allowed-ips", &is_set);
+ if (dlstatus != DLADM_STATUS_OK) {
+ err = EINVAL;
+ zerror(zlogp, B_FALSE, "unable to check if allowed-ips is set");
+ goto done;
+ }
+ if (is_set) {
+ zerror(zlogp, B_FALSE, "allowed-ips is already set");
+ err = EINVAL;
+ goto done;
+ }
+
+ /*
+ * Enable ip-nospoof for the link, and add address to the allowed-ips
+ * list.
+ */
+ dlstatus = dladm_set_linkprop(dld_handle, linkid, "protection",
+ &ip_nospoof, 1, DLADM_OPT_ACTIVE);
+ if (dlstatus != DLADM_STATUS_OK) {
+ zerror(zlogp, B_FALSE, "could not set protection\n");
+ err = EINVAL;
+ goto done;
+ }
+ dlstatus = dladm_set_linkprop(dld_handle, linkid, "allowed-ips",
+ astr, naddr, DLADM_OPT_ACTIVE);
+ if (dlstatus != DLADM_STATUS_OK) {
+ zerror(zlogp, B_FALSE, "could not set allowed-ips\n");
+ err = EINVAL;
+ goto done;
+ }
+
+ /* now set the address in the data-store */
+ err = zone_setattr_network(ZONE_NETWORK_ADDRESS, zoneid, linkid,
+ zaddr, strnlen(zaddr, zlen) + 1);
+ if (err != 0)
+ goto done;
+
+ /*
+ * add the defaultrouters
+ */
+ routes = calloc(1, nnet * sizeof (*routes));
+ j = 0;
+ for (ptr = start; ptr != end; ptr = ptr->za_next) {
+ address = ptr->za_nwiftab.zone_nwif_defrouter;
+ if (address[0] == '\0')
+ continue;
+ if (strchr(address, '/') == NULL && strchr(address, ':') != 0) {
+ /*
+ * zonecfg_valid_net_address() expects numeric IPv6
+ * addresses to have a CIDR format netmask.
+ */
+ (void) snprintf(tmp, sizeof (tmp), "/%d", V6_ADDR_LEN);
+ (void) strlcat(address, tmp, INET6_ADDRSTRLEN);
+ }
+ if (zonecfg_valid_net_address(address, &lifr) != Z_OK) {
+ zerror(zlogp, B_FALSE,
+ "invalid router [%s]\n", address);
+ err = EINVAL;
+ goto done;
+ }
+ if (lifr.lifr_addr.ss_family == AF_INET6) {
+ routes[j] = SIN6(&lifr.lifr_addr)->sin6_addr;
+ } else {
+ IN6_INADDR_TO_V4MAPPED(&SIN(&lifr.lifr_addr)->sin_addr,
+ &routes[j]);
+ }
+ j++;
+ }
+ assert(j <= nnet);
+ if (j > 0) {
+ err = zone_setattr_network(ZONE_NETWORK_DEFROUTER, zoneid,
+ linkid, routes, j * sizeof (*routes));
+ }
+done:
+ free(routes);
+ for (j = 0; j < naddr; j++)
+ free(astr[j]);
+ free(astr);
+ free(zaddr);
+ return (err);
+
+}
+
+static int
+add_net(zlog_t *zlogp, zoneid_t zoneid, zone_addr_list_t *zalist)
+{
+ zone_addr_list_t *ptr;
+ datalink_id_t linkid;
+ int err;
+
+ if (zalist == NULL)
+ return (0);
+
+ linkid = zalist->za_linkid;
+
+ err = add_net_for_linkid(zlogp, zoneid, zalist);
+ if (err != 0)
+ return (err);
+
+ for (ptr = zalist; ptr != NULL; ptr = ptr->za_next) {
+ if (ptr->za_linkid == linkid)
+ continue;
+ linkid = ptr->za_linkid;
+ err = add_net_for_linkid(zlogp, zoneid, ptr);
+ if (err != 0)
+ return (err);
+ }
+ return (0);
+}
+
+/*
+ * Add "new" to the list of network interfaces to be configured by
+ * add_net on zone boot in "old". The list of interfaces in "old" is
+ * sorted by datalink_id_t, with interfaces sorted FIFO for a given
+ * datalink_id_t.
+ *
+ * Returns the merged list of IP interfaces containing "old" and "new"
+ */
+static zone_addr_list_t *
+add_ip_interface(zone_addr_list_t *old, zone_addr_list_t *new)
+{
+ zone_addr_list_t *ptr, *next;
+ datalink_id_t linkid = new->za_linkid;
+
+ assert(old != new);
+
+ if (old == NULL)
+ return (new);
+ for (ptr = old; ptr != NULL; ptr = ptr->za_next) {
+ if (ptr->za_linkid == linkid)
+ break;
+ }
+ if (ptr == NULL) {
+ /* linkid does not already exist, add to the beginning */
+ new->za_next = old;
+ return (new);
+ }
+ /*
+ * adding to the middle of the list; ptr points at the first
+ * occurrence of linkid. Find the last occurrence.
+ */
+ while ((next = ptr->za_next) != NULL) {
+ if (next->za_linkid != linkid)
+ break;
+ ptr = next;
+ }
+ /* insert new after ptr */
+ new->za_next = next;
+ ptr->za_next = new;
+ return (old);
+}
+
+void
+free_ip_interface(zone_addr_list_t *zalist)
+{
+ zone_addr_list_t *ptr, *new;
+
+ for (ptr = zalist; ptr != NULL; ) {
+ new = ptr;
+ ptr = ptr->za_next;
+ free(new);
+ }
+}
+
/*
* Add the kernel access control information for the interface names.
* If anything goes wrong, we log a general error message, attempt to tear down
* whatever we set up, and return an error.
*/
static int
-configure_exclusive_network_interfaces(zlog_t *zlogp)
+configure_exclusive_network_interfaces(zlog_t *zlogp, zoneid_t zoneid)
{
zone_dochandle_t handle;
struct zone_nwiftab nwiftab;
@@ -2517,6 +2892,7 @@ configure_exclusive_network_interfaces(zlog_t *zlogp)
datalink_id_t linkid;
di_prof_t prof = NULL;
boolean_t added = B_FALSE;
+ zone_addr_list_t *zalist = NULL, *new;
if ((handle = zonecfg_init_handle()) == NULL) {
zerror(zlogp, B_TRUE, "getting zone configuration handle");
@@ -2579,6 +2955,28 @@ configure_exclusive_network_interfaces(zlog_t *zlogp)
zerror(zlogp, B_TRUE, "failed to add network device");
return (-1);
}
+ /* set up the new IP interface, and add them all later */
+ new = malloc(sizeof (*new));
+ if (new == NULL) {
+ zerror(zlogp, B_TRUE, "no memory for %s",
+ nwiftab.zone_nwif_physical);
+ zonecfg_fini_handle(handle);
+ free_ip_interface(zalist);
+ }
+ bzero(new, sizeof (*new));
+ new->za_nwiftab = nwiftab;
+ new->za_linkid = linkid;
+ zalist = add_ip_interface(zalist, new);
+ }
+ if (zalist != NULL) {
+ if ((errno = add_net(zlogp, zoneid, zalist)) != 0) {
+ (void) zonecfg_endnwifent(handle);
+ zonecfg_fini_handle(handle);
+ zerror(zlogp, B_TRUE, "failed to add address");
+ free_ip_interface(zalist);
+ return (-1);
+ }
+ free_ip_interface(zalist);
}
(void) zonecfg_endnwifent(handle);
zonecfg_fini_handle(handle);
@@ -2610,8 +3008,7 @@ remove_datalink_pool(zlog_t *zlogp, zoneid_t zoneid)
if (zone_getattr(zoneid, ZONE_ATTR_FLAGS, &flags,
sizeof (flags)) < 0) {
if (vplat_get_iptype(zlogp, &iptype) < 0) {
- zerror(zlogp, B_TRUE, "unable to determine "
- "ip-type");
+ zerror(zlogp, B_FALSE, "unable to determine ip-type");
return (-1);
}
} else {
@@ -2662,6 +3059,74 @@ remove_datalink_pool(zlog_t *zlogp, zoneid_t zoneid)
}
static int
+remove_datalink_protect(zlog_t *zlogp, zoneid_t zoneid)
+{
+ ushort_t flags;
+ zone_iptype_t iptype;
+ int i, dlnum = 0;
+ dladm_status_t dlstatus;
+ datalink_id_t *dllink, *dllinks = NULL;
+
+ if (zone_getattr(zoneid, ZONE_ATTR_FLAGS, &flags,
+ sizeof (flags)) < 0) {
+ if (vplat_get_iptype(zlogp, &iptype) < 0) {
+ zerror(zlogp, B_FALSE, "unable to determine ip-type");
+ return (-1);
+ }
+ } else {
+ if (flags & ZF_NET_EXCL)
+ iptype = ZS_EXCLUSIVE;
+ else
+ iptype = ZS_SHARED;
+ }
+
+ if (iptype != ZS_EXCLUSIVE)
+ return (0);
+
+ /*
+ * Get the datalink count and for each datalink,
+ * attempt to clear the pool property and clear
+ * the pool_name.
+ */
+ if (zone_list_datalink(zoneid, &dlnum, NULL) != 0) {
+ zerror(zlogp, B_TRUE, "unable to count network interfaces");
+ return (-1);
+ }
+
+ if (dlnum == 0)
+ return (0);
+
+ if ((dllinks = malloc(dlnum * sizeof (datalink_id_t))) == NULL) {
+ zerror(zlogp, B_TRUE, "memory allocation failed");
+ return (-1);
+ }
+ if (zone_list_datalink(zoneid, &dlnum, dllinks) != 0) {
+ zerror(zlogp, B_TRUE, "unable to list network interfaces");
+ free(dllinks);
+ return (-1);
+ }
+
+ for (i = 0, dllink = dllinks; i < dlnum; i++, dllink++) {
+ dlstatus = dladm_set_linkprop(dld_handle, *dllink,
+ "protection", NULL, 0, DLADM_OPT_ACTIVE);
+ if (dlstatus != DLADM_STATUS_OK) {
+ zerror(zlogp, B_TRUE, "could not reset protection\n");
+ free(dllinks);
+ return (-1);
+ }
+ dlstatus = dladm_set_linkprop(dld_handle, *dllink,
+ "allowed-ips", NULL, 0, DLADM_OPT_ACTIVE);
+ if (dlstatus != DLADM_STATUS_OK) {
+ zerror(zlogp, B_TRUE, "could not reset allowed-ips\n");
+ free(dllinks);
+ return (-1);
+ }
+ }
+ free(dllinks);
+ return (0);
+}
+
+static int
unconfigure_exclusive_network_interfaces(zlog_t *zlogp, zoneid_t zoneid)
{
int dlnum = 0;
@@ -4542,7 +5007,8 @@ vplat_bringup(zlog_t *zlogp, zone_mnt_t mount_cmd, zoneid_t zoneid)
}
break;
case ZS_EXCLUSIVE:
- if (configure_exclusive_network_interfaces(zlogp) !=
+ if (configure_exclusive_network_interfaces(zlogp,
+ zoneid) !=
0) {
lofs_discard_mnttab();
return (-1);
@@ -4667,6 +5133,19 @@ vplat_teardown(zlog_t *zlogp, boolean_t unmount_cmd, boolean_t rebooting)
goto error;
}
+ if (remove_datalink_protect(zlogp, zoneid) != 0) {
+ zerror(zlogp, B_FALSE,
+ "unable clear datalink protect property");
+ goto error;
+ }
+
+ /*
+ * The datalinks assigned to the zone will be removed from the NGZ as
+ * part of zone_shutdown() so that we need to remove protect/pool etc.
+ * before zone_shutdown(). Even if the shutdown itself fails, the zone
+ * will not be able to violate any constraints applied because the
+ * datalinks are no longer available to the zone.
+ */
if (zone_shutdown(zoneid) != 0) {
zerror(zlogp, B_TRUE, "unable to shutdown zone");
goto error;