changeset 3653:b023d36e83d9

6512526 RC1a: vntsd needs to validate the listen-to IP address 6512604 handshake untimeout() race condition in vnet 6517019 vgen_multicst does not handle kmem_zalloc failure 6520018 vntsd gets confused and immediately closes newly established console connections 6521890 recursive mutex_enter in ldc_set_cb_mode
author narayan
date Wed, 14 Feb 2007 19:32:07 -0800
parents fe555d5eb825
children 9c6042b08979
files usr/src/cmd/vntsd/listen.c usr/src/cmd/vntsd/vntsd.c usr/src/uts/sun4v/io/ldc.c usr/src/uts/sun4v/io/vnet_gen.c usr/src/uts/sun4v/sys/vnet_gen.h
diffstat 5 files changed, 152 insertions(+), 60 deletions(-) [+]
line wrap: on
line diff
--- a/usr/src/cmd/vntsd/listen.c	Wed Feb 14 19:20:08 2007 -0800
+++ b/usr/src/cmd/vntsd/listen.c	Wed Feb 14 19:32:07 2007 -0800
@@ -45,6 +45,7 @@
 #include "vntsd.h"
 
 #define	    MAX_BIND_RETRIES		6
+
 /*
  * check the state of listen thread. exit if there is an fatal error
  * or the group is removed. Main thread will call free_group
@@ -70,22 +71,13 @@
 	case VNTSD_SUCCESS:
 		return;
 
-	case VNTSD_STATUS_INTR:
-		/* signal for deleting group */
-		assert(groupp->status & VNTSD_GROUP_SIG_WAIT);
-
-		/* let main thread know  */
-		(void) mutex_lock(&groupp->lock);
-		groupp->status &= ~VNTSD_GROUP_SIG_WAIT;
-		(void) cond_signal(&groupp->cvp);
-		(void) mutex_unlock(&groupp->lock);
-
-		thr_exit(0);
-		break;
 
 	case VNTSD_STATUS_ACCEPT_ERR:
 		return;
 
+	case VNTSD_STATUS_INTR:
+		assert(groupp->status & VNTSD_GROUP_SIG_WAIT);
+		/*FALLTHRU*/
 	case VNTSD_STATUS_NO_CONS:
 	default:
 		/* fatal error or no console in the group, remove the group. */
@@ -93,9 +85,14 @@
 		(void) mutex_lock(&groupp->lock);
 
 		if (groupp->status & VNTSD_GROUP_SIG_WAIT) {
-			/* group is already in deletion */
+			/*
+			 * group is already being deleted, notify main
+			 * thread and exit.
+			 */
+			groupp->status &= ~VNTSD_GROUP_SIG_WAIT;
+			(void) cond_signal(&groupp->cvp);
 			(void) mutex_unlock(&groupp->lock);
-			return;
+			thr_exit(0);
 		}
 
 		/*
@@ -115,7 +112,7 @@
 		/* log error */
 		if (status != VNTSD_STATUS_NO_CONS)
 			vntsd_log(status, err_msg);
-		break;
+		thr_exit(0);
 	}
 }
 
--- a/usr/src/cmd/vntsd/vntsd.c	Wed Feb 14 19:20:08 2007 -0800
+++ b/usr/src/cmd/vntsd/vntsd.c	Wed Feb 14 19:32:07 2007 -0800
@@ -20,7 +20,7 @@
  */
 
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -56,6 +56,8 @@
 #include <libintl.h>
 #include <locale.h>
 #include <syslog.h>
+#include <sys/socket.h>
+#include <netdb.h>
 #include "vntsd.h"
 #include "chars.h"
 
@@ -71,6 +73,8 @@
 
 #define	MINUTE		60
 
+#define	VNTSD_INVALID_LISTEN_ADDR	    ((in_addr_t)-1)
+
 static vntsd_t *vntsdp;
 
 
@@ -259,6 +263,57 @@
 		    "[-p <listen address>] [-t <timeout in minutes>]\n"));
 }
 
+/*
+ * get_listen_ip_addr()
+ * check for a valid control domain ip address in format of xxx.xxx.xxx.xxx.
+ * if ip address is valid and is assigned to this host, return ip address
+ * or else return VNTSD_INVALID_LISTEN_ADDR.
+ */
+static in_addr_t
+get_listen_ip_addr(char *listen_addr)
+{
+	char host_name[MAXPATHLEN];
+	in_addr_t addr;
+	struct addrinfo hints;
+	struct addrinfo *res, *infop;
+	int err;
+	struct sockaddr_in *sa;
+
+	if (gethostname(host_name, MAXPATHLEN) != 0) {
+		syslog(LOG_ERR, "Can not get host name!");
+		return (VNTSD_INVALID_LISTEN_ADDR);
+	}
+
+	if ((int)(addr = inet_addr(listen_addr)) == -1)
+		/* bad IP address format */
+		return (VNTSD_INVALID_LISTEN_ADDR);
+
+	bzero(&hints, sizeof (hints));
+	hints.ai_family = PF_INET;
+	hints.ai_socktype = SOCK_STREAM;
+
+	err = getaddrinfo(host_name, NULL, &hints, &res);
+	if (err != 0) {
+		syslog(LOG_ERR, "getaddrinfo failed: %s", gai_strerror(err));
+		return (VNTSD_INVALID_LISTEN_ADDR);
+	}
+
+	infop = res;
+	while (infop != NULL) {
+		/* LINTED E_BAD_PTR_CAST_ALIGN */
+		sa = (struct sockaddr_in *)infop->ai_addr;
+		if (sa->sin_addr.s_addr == addr) {
+			/* ip address found */
+			freeaddrinfo(res);
+			return (addr);
+		}
+		infop = infop->ai_next;
+	}
+
+	/* ip address not found */
+	freeaddrinfo(res);
+	return (VNTSD_INVALID_LISTEN_ADDR);
+}
 
 #ifdef DEBUG
 #define	DEBUG_OPTIONS	"d"
@@ -340,12 +395,12 @@
 	} else if (strcmp(listen_addr, "any") == 0) {
 		vntsdp->ip_addr.s_addr = htonl(INADDR_ANY);
 	} else {
-		vntsdp->ip_addr.s_addr = inet_addr(listen_addr);
-		if (vntsdp->ip_addr.s_addr == (in_addr_t)(-1)) {
-			(void) fprintf(stderr,
-			    gettext("Invalid listen address '%s'\n"),
+		vntsdp->ip_addr.s_addr = get_listen_ip_addr(listen_addr);
+		if (vntsdp->ip_addr.s_addr == VNTSD_INVALID_LISTEN_ADDR) {
+			syslog(LOG_ERR,
+			    "Invalid listen address '%s'\n",
 			    listen_addr);
-			exit(1);
+			exit(2);
 		}
 	}
 
--- a/usr/src/uts/sun4v/io/ldc.c	Wed Feb 14 19:20:08 2007 -0800
+++ b/usr/src/uts/sun4v/io/ldc.c	Wed Feb 14 19:32:07 2007 -0800
@@ -3232,6 +3232,7 @@
 		mutex_enter(&ldcp->tx_lock);
 		i_ldc_reset(ldcp, B_TRUE);
 		mutex_exit(&ldcp->tx_lock);
+		mutex_exit(&ldcp->lock);
 		return (ECONNRESET);
 	}
 
--- a/usr/src/uts/sun4v/io/vnet_gen.c	Wed Feb 14 19:20:08 2007 -0800
+++ b/usr/src/uts/sun4v/io/vnet_gen.c	Wed Feb 14 19:32:07 2007 -0800
@@ -20,7 +20,7 @@
  */
 
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -773,7 +773,7 @@
 	vgen_ldclist_t		*ldclp;
 	void			*vnetp;
 	struct ether_addr	*addrp;
-	int			rv;
+	int			rv = DDI_FAILURE;
 	uint32_t		i;
 
 	vgenp = (vgen_t *)arg;
@@ -791,18 +791,16 @@
 	portp = vgenp->vsw_portp;
 	if (portp == NULL) {
 		RW_EXIT(&plistp->rwlock);
-		goto vgen_mcast_exit;
+		mutex_exit(&vgenp->lock);
+		return (rv);
 	}
 	ldclp = &portp->ldclist;
 
 	READ_ENTER(&ldclp->rwlock);
 
 	ldcp = ldclp->headp;
-	if (ldcp == NULL) {
-		RW_EXIT(&ldclp->rwlock);
-		RW_EXIT(&plistp->rwlock);
+	if (ldcp == NULL)
 		goto vgen_mcast_exit;
-	}
 
 	mutex_enter(&ldcp->cblock);
 
@@ -818,11 +816,12 @@
 		bcopy(mca, &(mcastmsg.mca), ETHERADDRL);
 		mcastmsg.set = add;
 		mcastmsg.count = 1;
-		rv = vgen_sendmsg(ldcp, (caddr_t)tagp, sizeof (mcastmsg),
-		    B_FALSE);
-		if (rv != VGEN_SUCCESS) {
+		if (vgen_sendmsg(ldcp, (caddr_t)tagp, sizeof (mcastmsg),
+		    B_FALSE) != VGEN_SUCCESS) {
 			DWARN((vnetp, "vgen_mutlicst: vgen_sendmsg failed"
 			    "id (%lx)\n", ldcp->ldc_id));
+			mutex_exit(&ldcp->cblock);
+			goto vgen_mcast_exit;
 		}
 	} else {
 		/* set the flag to send a msg to vsw after handshake is done */
@@ -843,7 +842,8 @@
 
 			newtab = kmem_zalloc(newsize *
 			    sizeof (struct ether_addr), KM_NOSLEEP);
-
+			if (newtab == NULL)
+				goto vgen_mcast_exit;
 			bcopy(vgenp->mctab, newtab, vgenp->mcsize *
 			    sizeof (struct ether_addr));
 			kmem_free(vgenp->mctab,
@@ -878,12 +878,14 @@
 		}
 	}
 
+	rv = DDI_SUCCESS;
+
+vgen_mcast_exit:
 	RW_EXIT(&ldclp->rwlock);
 	RW_EXIT(&plistp->rwlock);
 
-vgen_mcast_exit:
 	mutex_exit(&vgenp->lock);
-	return (DDI_SUCCESS);
+	return (rv);
 }
 
 /* set or clear promiscuous mode on the device */
@@ -1965,20 +1967,32 @@
 		    "ldc_set_cb_mode failed\n", ldcp->ldc_id));
 	}
 
-	/* clear handshake done bit and wait for pending tx and cb to finish */
+	/*
+	 * clear handshake done bit and wait for pending tx and cb to finish.
+	 * release locks before untimeout(9F) is invoked to cancel timeouts.
+	 */
 	ldcp->hphase &= ~(VH_DONE);
 	LDC_UNLOCK(ldcp);
-	drv_usecwait(1000);
-	LDC_LOCK(ldcp);
-
-	vgen_reset_hphase(ldcp);
-
-	/* reset transmit watchdog timeout */
+
+	/* cancel handshake watchdog timeout */
+	if (ldcp->htid) {
+		(void) untimeout(ldcp->htid);
+		ldcp->htid = 0;
+	}
+
+	/* cancel transmit watchdog timeout */
 	if (ldcp->wd_tid) {
 		(void) untimeout(ldcp->wd_tid);
 		ldcp->wd_tid = 0;
 	}
 
+	drv_usecwait(1000);
+
+	/* acquire locks again; any pending transmits and callbacks are done */
+	LDC_LOCK(ldcp);
+
+	vgen_reset_hphase(ldcp);
+
 	vgen_uninit_tbufs(ldcp);
 
 	rv = ldc_close(ldcp->ldc_handle);
@@ -2512,7 +2526,7 @@
 	}
 
 	mutex_exit(&ldcp->cblock);
-	return (LDC_SUCCESS);
+	goto vgen_ldccb_exit;
 
 vgen_ldccb_rcv:
 
@@ -2526,7 +2540,7 @@
 			    "vgen_ldc_cb:ldc_read err id(%lx) rv(%d) "
 			    "len(%d)\n", ldcp->ldc_id, rv, msglen));
 			if (rv == ECONNRESET)
-				goto exit_error;
+				goto vgen_ldccb_error;
 			break;
 		}
 		if (msglen == 0) {
@@ -2558,7 +2572,7 @@
 				 * reset the channel.
 				 */
 				ldcp->need_ldc_reset = B_TRUE;
-				goto exit_error;
+				goto vgen_ldccb_error;
 			}
 		}
 
@@ -2593,7 +2607,7 @@
 			break;
 		}
 
-exit_error:
+vgen_ldccb_error:
 		if (rv == ECONNRESET) {
 			if (ldc_status(ldcp->ldc_handle, &istatus) != 0) {
 				DWARN((vnetp,
@@ -2621,6 +2635,20 @@
 		    ldcp->ldc_id, MBLKL(mp)));
 		vnet_rx(vgenp->vnetp, NULL, mp);
 	}
+
+vgen_ldccb_exit:
+	if (ldcp->cancel_htid) {
+		/*
+		 * Cancel handshake timer.
+		 * untimeout(9F) will not return until the pending callback is
+		 * cancelled or has run. No problems will result from calling
+		 * untimeout if the handler has already completed.
+		 * If the timeout handler did run, then it would just
+		 * return as cancel_htid is set.
+		 */
+		(void) untimeout(ldcp->cancel_htid);
+		ldcp->cancel_htid = 0;
+	}
 	DBG1((vnetp, "vgen_ldc_cb exit: ldcid (%lx)\n", ldcp->ldc_id));
 
 	return (LDC_SUCCESS);
@@ -3055,9 +3083,13 @@
 	ldcp->hstate = 0;
 	ldcp->hphase = VH_PHASE0;
 
-	/* reset handshake watchdog timeout */
+	/*
+	 * Save the id of pending handshake timer in cancel_htid.
+	 * This will be checked in vgen_ldc_cb() and the handshake timer will
+	 * be cancelled after releasing cblock.
+	 */
 	if (ldcp->htid) {
-		(void) untimeout(ldcp->htid);
+		ldcp->cancel_htid = ldcp->htid;
 		ldcp->htid = 0;
 	}
 
@@ -3149,13 +3181,6 @@
 		} else {
 			ldcp->ldc_status = istatus;
 		}
-
-		/* if channel is already UP - restart handshake */
-		if (istatus == LDC_UP) {
-			/* Initialize local session id */
-			ldcp->local_sid = ddi_get_lbolt();
-			vgen_handshake(vh_nextphase(ldcp));
-		}
 	}
 }
 
@@ -3214,9 +3239,13 @@
 		break;
 
 	case VH_DONE:
-		/* reset handshake watchdog timeout */
+		/*
+		 * Save the id of pending handshake timer in cancel_htid.
+		 * This will be checked in vgen_ldc_cb() and the handshake
+		 * timer will be cancelled after releasing cblock.
+		 */
 		if (ldcp->htid) {
-			(void) untimeout(ldcp->htid);
+			ldcp->cancel_htid = ldcp->htid;
 			ldcp->htid = 0;
 		}
 		ldcp->hretries = 0;
@@ -3329,9 +3358,13 @@
 {
 	/* reset handshake phase */
 	vgen_handshake_reset(ldcp);
-	if (vgen_max_hretries) {	/* handshake retry is specified */
-		if (ldcp->hretries++ < vgen_max_hretries)
+
+	/* handshake retry is specified and the channel is UP */
+	if (vgen_max_hretries && (ldcp->ldc_status == LDC_UP)) {
+		if (ldcp->hretries++ < vgen_max_hretries) {
+			ldcp->local_sid = ddi_get_lbolt();
 			vgen_handshake(vh_nextphase(ldcp));
+		}
 	}
 }
 
@@ -4850,7 +4883,7 @@
 #endif
 		mutex_enter(&ldcp->cblock);
 		ldcp->need_ldc_reset = B_TRUE;
-		vgen_handshake_reset(ldcp);
+		vgen_handshake_retry(ldcp);
 		mutex_exit(&ldcp->cblock);
 		if (ldcp->need_resched) {
 			ldcp->need_resched = B_FALSE;
@@ -5112,6 +5145,11 @@
 	    ldcp->ldc_id, ldcp->hphase, ldcp->hstate));
 
 	mutex_enter(&ldcp->cblock);
+	if (ldcp->cancel_htid) {
+		ldcp->cancel_htid = 0;
+		mutex_exit(&ldcp->cblock);
+		return;
+	}
 	ldcp->htid = 0;
 	ldcp->need_ldc_reset = B_TRUE;
 	vgen_handshake_retry(ldcp);
--- a/usr/src/uts/sun4v/sys/vnet_gen.h	Wed Feb 14 19:20:08 2007 -0800
+++ b/usr/src/uts/sun4v/sys/vnet_gen.h	Wed Feb 14 19:32:07 2007 -0800
@@ -20,7 +20,7 @@
  */
 
 /*
- * Copyright 2006 Sun Microsystems, Inc.  All rights reserved.
+ * Copyright 2007 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
  */
 
@@ -247,6 +247,7 @@
 	vgen_hparams_t		local_hparams;	/* local handshake params */
 	vgen_hparams_t		peer_hparams;	/* peer's handshake params */
 	timeout_id_t		htid;		/* handshake wd timeout id */
+	timeout_id_t		cancel_htid;	/* cancel handshake watchdog */
 
 	/* transmit and receive descriptor ring info */
 	ldc_dring_handle_t	tx_dhandle;	/* tx descriptor ring handle */