usr/src/uts/common/os/zone.c
changeset 3448 aaf16568054b
parent 3446 5903aece022d
child 3671 48011e38989d
--- a/usr/src/uts/common/os/zone.c	Fri Jan 19 15:20:53 2007 -0800
+++ b/usr/src/uts/common/os/zone.c	Fri Jan 19 16:59:38 2007 -0800
@@ -239,8 +239,7 @@
 #include <sys/fss.h>
 #include <sys/brand.h>
 #include <sys/zone.h>
-#include <sys/tsol/label.h>
-
+#include <net/if.h>
 #include <vm/seg.h>
 
 /*
@@ -345,6 +344,10 @@
 const char * const zone_default_initname = "/sbin/init";
 static char * const zone_prefix = "/zone/";
 static int zone_shutdown(zoneid_t zoneid);
+static int zone_add_datalink(zoneid_t, char *);
+static int zone_remove_datalink(zoneid_t, char *);
+static int zone_check_datalink(zoneid_t *, char *);
+static int zone_list_datalink(zoneid_t, int *, char *);
 
 /*
  * Bump this number when you alter the zone syscall interfaces; this is
@@ -361,8 +364,9 @@
  *     Trusted Extensions.
  * Version 5 alters the zone_boot system call, and converts its old
  *     bootargs parameter to be set by the zone_setattr API instead.
+ * Version 6 adds the flag argument to zone_create.
  */
-static const int ZONE_SYSCALL_API_VERSION = 5;
+static const int ZONE_SYSCALL_API_VERSION = 6;
 
 /*
  * Certain filesystems (such as NFS and autofs) need to know which zone
@@ -3196,7 +3200,8 @@
     const priv_set_t *zone_privs, size_t zone_privssz,
     caddr_t rctlbuf, size_t rctlbufsz,
     caddr_t zfsbuf, size_t zfsbufsz, int *extended_error,
-    int match, uint32_t doi, const bslabel_t *label)
+    int match, uint32_t doi, const bslabel_t *label,
+    int flags)
 {
 	struct zsched_arg zarg;
 	nvlist_t *rctls = NULL;
@@ -3238,6 +3243,10 @@
 	    offsetof(zone_dataset_t, zd_linkage));
 	rw_init(&zone->zone_mlps.mlpl_rwlock, NULL, RW_DEFAULT, NULL);
 
+	if (flags & ZCF_NET_EXCL) {
+		zone->zone_flags |= ZF_NET_EXCL;
+	}
+
 	if ((error = zone_set_name(zone, zone_name)) != 0) {
 		zone_free(zone);
 		return (zone_create_error(error, 0, extended_error));
@@ -3826,6 +3835,7 @@
 	 */
 	zone_status_wait(zone, ZONE_IS_DEAD);
 	zone_zsd_callbacks(zone, ZSD_DESTROY);
+	zone->zone_netstack = NULL;
 	uniqid = zone->zone_uniqid;
 	zone_rele(zone);
 	zone = NULL;	/* potentially free'd */
@@ -3923,6 +3933,7 @@
 	pid_t initpid;
 	boolean_t global = (curproc->p_zone == global_zone);
 	boolean_t curzone = (curproc->p_zone->zone_id == zoneid);
+	ushort_t flags;
 
 	mutex_enter(&zonehash_lock);
 	if ((zone = zone_find_all_by_id(zoneid)) == NULL) {
@@ -4021,6 +4032,15 @@
 		    copyout(&zone_status, buf, bufsize) != 0)
 			error = EFAULT;
 		break;
+	case ZONE_ATTR_FLAGS:
+		size = sizeof (zone->zone_flags);
+		if (bufsize > size)
+			bufsize = size;
+		flags = zone->zone_flags;
+		if (buf != NULL &&
+		    copyout(&flags, buf, bufsize) != 0)
+			error = EFAULT;
+		break;
 	case ZONE_ATTR_PRIVSET:
 		size = sizeof (priv_set_t);
 		if (bufsize > size)
@@ -4877,6 +4897,7 @@
 			zs.match = zs32.match;
 			zs.doi = zs32.doi;
 			zs.label = (const bslabel_t *)(uintptr_t)zs32.label;
+			zs.flags = zs32.flags;
 #else
 			panic("get_udatamodel() returned bogus result\n");
 #endif
@@ -4887,7 +4908,7 @@
 		    (caddr_t)zs.rctlbuf, zs.rctlbufsz,
 		    (caddr_t)zs.zfsbuf, zs.zfsbufsz,
 		    zs.extended_error, zs.match, zs.doi,
-		    zs.label));
+		    zs.label, zs.flags));
 	case ZONE_BOOT:
 		return (zone_boot((zoneid_t)(uintptr_t)arg1));
 	case ZONE_DESTROY:
@@ -4908,6 +4929,17 @@
 		return (zone_lookup((const char *)arg1));
 	case ZONE_VERSION:
 		return (zone_version((int *)arg1));
+	case ZONE_ADD_DATALINK:
+		return (zone_add_datalink((zoneid_t)(uintptr_t)arg1,
+		    (char *)arg2));
+	case ZONE_DEL_DATALINK:
+		return (zone_remove_datalink((zoneid_t)(uintptr_t)arg1,
+		    (char *)arg2));
+	case ZONE_CHECK_DATALINK:
+		return (zone_check_datalink((zoneid_t *)arg1, (char *)arg2));
+	case ZONE_LIST_DATALINK:
+		return (zone_list_datalink((zoneid_t)(uintptr_t)arg1,
+		    (int *)arg2, (char *)arg3));
 	default:
 		return (set_errno(EINVAL));
 	}
@@ -5298,3 +5330,251 @@
 	mutex_exit(&zonehash_lock);
 	return (zone);
 }
+
+/* List of data link names which are accessible from the zone */
+struct dlnamelist {
+	char			dlnl_name[LIFNAMSIZ];
+	struct dlnamelist	*dlnl_next;
+};
+
+
+/*
+ * Check whether the datalink name (dlname) itself is present.
+ * Return true if found.
+ */
+static boolean_t
+zone_dlname(zone_t *zone, char *dlname)
+{
+	struct dlnamelist *dlnl;
+	boolean_t found = B_FALSE;
+
+	mutex_enter(&zone->zone_lock);
+	for (dlnl = zone->zone_dl_list; dlnl != NULL; dlnl = dlnl->dlnl_next) {
+		if (strncmp(dlnl->dlnl_name, dlname, LIFNAMSIZ) == 0) {
+			found = B_TRUE;
+			break;
+		}
+	}
+	mutex_exit(&zone->zone_lock);
+	return (found);
+}
+
+/*
+ * Add an data link name for the zone. Does not check for duplicates.
+ */
+static int
+zone_add_datalink(zoneid_t zoneid, char *dlname)
+{
+	struct dlnamelist *dlnl;
+	zone_t *zone;
+	zone_t *thiszone;
+	int err;
+
+	dlnl = kmem_zalloc(sizeof (struct dlnamelist), KM_SLEEP);
+	if ((err = copyinstr(dlname, dlnl->dlnl_name, LIFNAMSIZ, NULL)) != 0) {
+		kmem_free(dlnl, sizeof (struct dlnamelist));
+		return (set_errno(err));
+	}
+
+	thiszone = zone_find_by_id(zoneid);
+	if (thiszone == NULL) {
+		kmem_free(dlnl, sizeof (struct dlnamelist));
+		return (set_errno(ENXIO));
+	}
+
+	/*
+	 * Verify that the datalink name isn't already used by a different
+	 * zone while allowing duplicate entries for the same zone (e.g. due
+	 * to both using IPv4 and IPv6 on an interface)
+	 */
+	mutex_enter(&zonehash_lock);
+	for (zone = list_head(&zone_active); zone != NULL;
+	    zone = list_next(&zone_active, zone)) {
+		if (zone->zone_id == zoneid)
+			continue;
+
+		if (zone_dlname(zone, dlnl->dlnl_name)) {
+			mutex_exit(&zonehash_lock);
+			zone_rele(thiszone);
+			kmem_free(dlnl, sizeof (struct dlnamelist));
+			return (set_errno(EPERM));
+		}
+	}
+	mutex_enter(&thiszone->zone_lock);
+	dlnl->dlnl_next = thiszone->zone_dl_list;
+	thiszone->zone_dl_list = dlnl;
+	mutex_exit(&thiszone->zone_lock);
+	mutex_exit(&zonehash_lock);
+	zone_rele(thiszone);
+	return (0);
+}
+
+static int
+zone_remove_datalink(zoneid_t zoneid, char *dlname)
+{
+	struct dlnamelist *dlnl, *odlnl, **dlnlp;
+	zone_t *zone;
+	int err;
+
+	dlnl = kmem_zalloc(sizeof (struct dlnamelist), KM_SLEEP);
+	if ((err = copyinstr(dlname, dlnl->dlnl_name, LIFNAMSIZ, NULL)) != 0) {
+		kmem_free(dlnl, sizeof (struct dlnamelist));
+		return (set_errno(err));
+	}
+	zone = zone_find_by_id(zoneid);
+	if (zone == NULL) {
+		kmem_free(dlnl, sizeof (struct dlnamelist));
+		return (set_errno(EINVAL));
+	}
+
+	mutex_enter(&zone->zone_lock);
+	/* Look for match */
+	dlnlp = &zone->zone_dl_list;
+	while (*dlnlp != NULL) {
+		if (strncmp(dlnl->dlnl_name, (*dlnlp)->dlnl_name,
+		    LIFNAMSIZ) == 0)
+			goto found;
+		dlnlp = &((*dlnlp)->dlnl_next);
+	}
+	mutex_exit(&zone->zone_lock);
+	zone_rele(zone);
+	kmem_free(dlnl, sizeof (struct dlnamelist));
+	return (set_errno(ENXIO));
+
+found:
+	odlnl = *dlnlp;
+	*dlnlp = (*dlnlp)->dlnl_next;
+	kmem_free(odlnl, sizeof (struct dlnamelist));
+
+	mutex_exit(&zone->zone_lock);
+	zone_rele(zone);
+	kmem_free(dlnl, sizeof (struct dlnamelist));
+	return (0);
+}
+
+/*
+ * Using the zoneidp as ALL_ZONES, we can lookup which zone is using datalink
+ * name (dlname); otherwise we just check if the specified zoneidp has access
+ * to the datalink name.
+ */
+static int
+zone_check_datalink(zoneid_t *zoneidp, char *dlname)
+{
+	zoneid_t id;
+	char *dln;
+	zone_t *zone;
+	int err = 0;
+	boolean_t allzones = B_FALSE;
+
+	if (copyin(zoneidp, &id, sizeof (id)) != 0) {
+		return (set_errno(EFAULT));
+	}
+	dln = kmem_zalloc(LIFNAMSIZ, KM_SLEEP);
+	if ((err = copyinstr(dlname, dln, LIFNAMSIZ, NULL)) != 0) {
+		kmem_free(dln, LIFNAMSIZ);
+		return (set_errno(err));
+	}
+
+	if (id == ALL_ZONES)
+		allzones = B_TRUE;
+
+	/*
+	 * Check whether datalink name is already used.
+	 */
+	mutex_enter(&zonehash_lock);
+	for (zone = list_head(&zone_active); zone != NULL;
+	    zone = list_next(&zone_active, zone)) {
+		if (allzones || (id == zone->zone_id)) {
+			if (!zone_dlname(zone, dln))
+				continue;
+			if (allzones)
+				err = copyout(&zone->zone_id, zoneidp,
+				    sizeof (*zoneidp));
+
+			mutex_exit(&zonehash_lock);
+			kmem_free(dln, LIFNAMSIZ);
+			return (err ? set_errno(EFAULT) : 0);
+		}
+	}
+
+	/* datalink name is not found in any active zone. */
+	mutex_exit(&zonehash_lock);
+	kmem_free(dln, LIFNAMSIZ);
+	return (set_errno(ENXIO));
+}
+
+/*
+ * Get the names of the datalinks assigned to a zone.
+ * Here *nump is the number of datalinks, and the assumption
+ * is that the caller will gurantee that the the supplied buffer is
+ * big enough to hold at least #*nump datalink names, that is,
+ * LIFNAMSIZ X *nump
+ * On return, *nump will be the "new" number of datalinks, if it
+ * ever changed.
+ */
+static int
+zone_list_datalink(zoneid_t zoneid, int *nump, char *buf)
+{
+	int num, dlcount;
+	zone_t *zone;
+	struct dlnamelist *dlnl;
+	char *ptr;
+
+	if (copyin(nump, &dlcount, sizeof (dlcount)) != 0)
+		return (set_errno(EFAULT));
+
+	zone = zone_find_by_id(zoneid);
+	if (zone == NULL) {
+		return (set_errno(ENXIO));
+	}
+
+	num = 0;
+	mutex_enter(&zone->zone_lock);
+	ptr = buf;
+	for (dlnl = zone->zone_dl_list; dlnl != NULL; dlnl = dlnl->dlnl_next) {
+		/*
+		 * If the list changed and the new number is bigger
+		 * than what the caller supplied, just count, don't
+		 * do copyout
+		 */
+		if (++num > dlcount)
+			continue;
+		if (copyout(dlnl->dlnl_name, ptr, LIFNAMSIZ) != 0) {
+			mutex_exit(&zone->zone_lock);
+			zone_rele(zone);
+			return (set_errno(EFAULT));
+		}
+		ptr += LIFNAMSIZ;
+	}
+	mutex_exit(&zone->zone_lock);
+	zone_rele(zone);
+
+	/* Increased or decreased, caller should be notified. */
+	if (num != dlcount) {
+		if (copyout(&num, nump, sizeof (num)) != 0) {
+			return (set_errno(EFAULT));
+		}
+	}
+	return (0);
+}
+
+/*
+ * Public interface for looking up a zone by zoneid. It's a customized version
+ * for netstack_zone_create(), it:
+ * 1. Doesn't acquire the zonehash_lock, since it is called from
+ *    zone_key_create() or zone_zsd_configure(), lock already held.
+ * 2. Doesn't check the status of the zone.
+ * 3. It will be called even before zone_init is called, in that case the
+ *    address of zone0 is returned directly, and netstack_zone_create()
+ *    will only assign a value to zone0.zone_netstack, won't break anything.
+ */
+zone_t *
+zone_find_by_id_nolock(zoneid_t zoneid)
+{
+	ASSERT(MUTEX_HELD(&zonehash_lock));
+
+	if (zonehashbyid == NULL)
+		return (&zone0);
+	else
+		return (zone_find_all_by_id(zoneid));
+}