Print this page
NEX-19225 SMB client 2.1 hits redzone panic
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
NEX-14666 Need to provide SMB 2.1 Client
NEX-17187 panic in smbfs_acl_store
NEX-17231 smbfs create xattr files finds wrong file
NEX-17224 smbfs lookup EINVAL should be ENOENT
NEX-17260 SMB1 client fails to list directory after NEX-14666
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Rick McNeal <rick.mcneal@nexenta.com>
Reviewed by: Saso Kiselkov <saso.kiselkov@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
and: (cleanup)
NEX-16824 SMB client connection setup rework
NEX-17232 SMB client reconnect failures
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
and: (improve debug)
NEX-16818 Add fksmbcl development tool
NEX-17264 SMB client test tp_smbutil_013 fails after NEX-14666
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
and: (fix ref leaks)
NEX-16805 Add smbutil discon command
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
SUP-548 Panic from NULL pointer dereference in smb_iod_disconnect

@@ -33,10 +33,13 @@
  */
 
 /*
  * Copyright 2009 Sun Microsystems, Inc.  All rights reserved.
  * Use is subject to license terms.
+ *
+ * Portions Copyright (C) 2001 - 2013 Apple Inc. All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #ifdef DEBUG
 /* See sys/queue.h */
 #define QUEUEDEBUG 1

@@ -65,18 +68,31 @@
 #include <sys/sdt.h>
 
 #include <netsmb/smb_osdep.h>
 
 #include <netsmb/smb.h>
+#include <netsmb/smb2.h>
 #include <netsmb/smb_conn.h>
 #include <netsmb/smb_rq.h>
+#include <netsmb/smb2_rq.h>
 #include <netsmb/smb_subr.h>
 #include <netsmb/smb_tran.h>
 #include <netsmb/smb_trantcp.h>
 
-int smb_iod_send_echo(smb_vc_t *);
+/*
+ * SMB messages are up to 64K.  Let's leave room for two.
+ * If we negotiate up to SMB2, increase these. XXX todo
+ */
+static int smb_tcpsndbuf = 0x20000;
+static int smb_tcprcvbuf = 0x20000;
+static int smb_connect_timeout = 10; /* seconds */
 
+static int smb1_iod_process(smb_vc_t *, mblk_t *);
+static int smb2_iod_process(smb_vc_t *, mblk_t *);
+static int smb_iod_send_echo(smb_vc_t *, cred_t *cr);
+static int smb_iod_logoff(struct smb_vc *vcp, cred_t *cr);
+
 /*
  * This is set/cleared when smbfs loads/unloads
  * No locks should be necessary, because smbfs
  * can't unload until all the mounts are gone.
  */

@@ -91,11 +107,14 @@
 smb_iod_share_disconnected(smb_share_t *ssp)
 {
 
         smb_share_invalidate(ssp);
 
-        /* smbfs_dead() */
+        /*
+         * This is the only fscb hook smbfs currently uses.
+         * Replaces smbfs_dead() from Darwin.
+         */
         if (fscb && fscb->fscb_disconn) {
                 fscb->fscb_disconn(ssp);
         }
 }
 

@@ -140,23 +159,26 @@
 {
         struct smb_rq *rqp;
 
         /*
          * Invalidate all outstanding requests for this connection
+         * Also wakeup iod_muxwant waiters.
          */
         rw_enter(&vcp->iod_rqlock, RW_READER);
         TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
                 smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART);
         }
         rw_exit(&vcp->iod_rqlock);
+        cv_broadcast(&vcp->iod_muxwait);
 }
 
 /*
- * Called by smb_vc_rele, smb_vc_kill, and by the driver
- * close entry point if the IOD closes its dev handle.
+ * Called by smb_vc_rele/smb_vc_kill on last ref, and by
+ * the driver close function if the IOD closes its minor.
+ * In those cases, the caller should be the IOD thread.
  *
- * Forcibly kill the connection and IOD.
+ * Forcibly kill the connection.
  */
 void
 smb_iod_disconnect(struct smb_vc *vcp)
 {
 

@@ -168,216 +190,266 @@
                 smb_iod_newstate(vcp, SMBIOD_ST_DEAD);
                 cv_broadcast(&vcp->vc_statechg);
         }
         SMB_VC_UNLOCK(vcp);
 
-        /*
-         * Let's be safe here and avoid doing any
-         * call across the network while trying to
-         * shut things down.  If we just disconnect,
-         * the server will take care of the logoff.
-         */
         SMB_TRAN_DISCONNECT(vcp);
-
-        /*
-         * If we have an IOD, it should immediately notice
-         * that its connection has closed.  But in case
-         * it doesn't, let's also send it a signal.
-         */
-        SMB_VC_LOCK(vcp);
-        if (vcp->iod_thr != NULL &&
-            vcp->iod_thr != curthread) {
-                tsignal(vcp->iod_thr, SIGKILL);
-        }
-        SMB_VC_UNLOCK(vcp);
 }
 
 /*
  * Send one request.
  *
+ * SMB1 only
+ *
  * Called by _addrq (for internal requests)
  * and _sendall (via _addrq, _multirq, _waitrq)
+ * Errors are reported via the smb_rq, using:
+ *   smb_iod_rqprocessed(rqp, ...)
  */
-static int
-smb_iod_sendrq(struct smb_rq *rqp)
+static void
+smb1_iod_sendrq(struct smb_rq *rqp)
 {
         struct smb_vc *vcp = rqp->sr_vc;
         mblk_t *m;
         int error;
 
         ASSERT(vcp);
-        ASSERT(SEMA_HELD(&vcp->vc_sendlock));
-        ASSERT(RW_READ_HELD(&vcp->iod_rqlock));
+        ASSERT(RW_WRITE_HELD(&vcp->iod_rqlock));
+        ASSERT((vcp->vc_flags & SMBV_SMB2) == 0);
 
         /*
-         * Note: Anything special for SMBR_INTERNAL here?
+         * Internal requests are allowed in any state;
+         * otherwise should be active.
          */
-        if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
+        if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
+            vcp->vc_state != SMBIOD_ST_VCACTIVE) {
                 SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
-                return (ENOTCONN);
+                smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART);
+                return;
         }
 
+        /*
+         * Overwrite the SMB header with the assigned MID and
+         * (if we're signing) sign it.
+         */
+        smb_rq_fillhdr(rqp);
+        if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) {
+                smb_rq_sign(rqp);
+        }
 
         /*
-         * On the first send, set the MID and (maybe)
-         * the signing sequence numbers.  The increments
-         * here are serialized by vc_sendlock
+         * The transport send consumes the message and we'd
+         * prefer to keep a copy, so dupmsg() before sending.
          */
-        if (rqp->sr_sendcnt == 0) {
+        m = dupmsg(rqp->sr_rq.mb_top);
+        if (m == NULL) {
+                error = ENOBUFS;
+                goto fatal;
+        }
 
-                rqp->sr_mid = vcp->vc_next_mid++;
+#ifdef DTRACE_PROBE2
+        DTRACE_PROBE2(iod_sendrq,
+            (smb_rq_t *), rqp, (mblk_t *), m);
+#endif
 
-                if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) {
+        error = SMB_TRAN_SEND(vcp, m);
+        m = 0; /* consumed by SEND */
+
+        rqp->sr_lerror = error;
+        if (error == 0) {
+                SMBRQ_LOCK(rqp);
+                rqp->sr_flags |= SMBR_SENT;
+                rqp->sr_state = SMBRQ_SENT;
+                SMBRQ_UNLOCK(rqp);
+                return;
+        }
                         /*
-                         * We're signing requests and verifying
-                         * signatures on responses.  Set the
-                         * sequence numbers of the request and
-                         * response here, used in smb_rq_verify.
+         * Transport send returned an error.
+         * Was it a fatal one?
                          */
-                        rqp->sr_seqno = vcp->vc_next_seq++;
-                        rqp->sr_rseqno = vcp->vc_next_seq++;
+        if (SMB_TRAN_FATAL(vcp, error)) {
+                /*
+                 * No further attempts should be made
+                 */
+        fatal:
+                SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error);
+                smb_iod_rqprocessed(rqp, error, SMBR_RESTART);
+                return;
                 }
+}
 
-                /* Fill in UID, TID, MID, etc. */
-                smb_rq_fillhdr(rqp);
+/*
+ * Send one request.
+ *
+ * SMB2 only
+ *
+ * Called by _addrq (for internal requests)
+ * and _sendall (via _addrq, _multirq, _waitrq)
+ * Errors are reported via the smb_rq, using:
+ *   smb_iod_rqprocessed(rqp, ...)
+ */
+static void
+smb2_iod_sendrq(struct smb_rq *rqp)
+{
+        struct smb_rq *c_rqp;   /* compound */
+        struct smb_vc *vcp = rqp->sr_vc;
+        mblk_t *top_m;
+        mblk_t *cur_m;
+        int error;
 
+        ASSERT(vcp);
+        ASSERT(RW_WRITE_HELD(&vcp->iod_rqlock));
+        ASSERT((vcp->vc_flags & SMBV_SMB2) != 0);
+
                 /*
-                 * Sign the message now that we're finally done
-                 * filling in the SMB header fields, etc.
+         * Internal requests are allowed in any state;
+         * otherwise should be active.
                  */
-                if (rqp->sr_rqflags2 & SMB_FLAGS2_SECURITY_SIGNATURE) {
-                        smb_rq_sign(rqp);
+        if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
+            vcp->vc_state != SMBIOD_ST_VCACTIVE) {
+                SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
+                smb_iod_rqprocessed(rqp, ENOTCONN, SMBR_RESTART);
+                return;
                 }
-        }
-        if (rqp->sr_sendcnt++ >= 60/SMBSBTIMO) { /* one minute */
-                smb_iod_rqprocessed(rqp, rqp->sr_lerror, SMBR_RESTART);
+
                 /*
-                 * If all attempts to send a request failed, then
-                 * something is seriously hosed.
+         * Overwrite the SMB header with the assigned MID and
+         * (if we're signing) sign it.  If there are compounded
+         * requests after the top one, do those too.
                  */
-                return (ENOTCONN);
+        smb2_rq_fillhdr(rqp);
+        if (rqp->sr2_rqflags & SMB2_FLAGS_SIGNED) {
+                smb2_rq_sign(rqp);
         }
+        c_rqp = rqp->sr2_compound_next;
+        while (c_rqp != NULL) {
+                smb2_rq_fillhdr(c_rqp);
+                if (c_rqp->sr2_rqflags & SMB2_FLAGS_SIGNED) {
+                        smb2_rq_sign(c_rqp);
+                }
+                c_rqp = c_rqp->sr2_compound_next;
+        }
 
         /*
-         * Replaced m_copym() with Solaris copymsg() which does the same
-         * work when we want to do a M_COPYALL.
-         * m = m_copym(rqp->sr_rq.mb_top, 0, M_COPYALL, 0);
+         * The transport send consumes the message and we'd
+         * prefer to keep a copy, so dupmsg() before sending.
+         * We also need this to build the compound message
+         * that we'll actually send.  The message offset at
+         * the start of each compounded message should be
+         * eight-byte aligned.  The caller preparing the
+         * compounded request has to take care of that
+         * before we get here and sign messages etc.
          */
-        m = copymsg(rqp->sr_rq.mb_top);
+        top_m = dupmsg(rqp->sr_rq.mb_top);
+        if (top_m == NULL) {
+                error = ENOBUFS;
+                goto fatal;
+        }
+        c_rqp = rqp->sr2_compound_next;
+        while (c_rqp != NULL) {
+                size_t len = msgdsize(top_m);
+                ASSERT((len & 7) == 0);
+                cur_m = dupmsg(c_rqp->sr_rq.mb_top);
+                if (cur_m == NULL) {
+                        freemsg(top_m);
+                        error = ENOBUFS;
+                        goto fatal;
+                }
+                linkb(top_m, cur_m);
+        }
 
-#ifdef DTRACE_PROBE
-        DTRACE_PROBE2(smb_iod_sendrq,
-            (smb_rq_t *), rqp, (mblk_t *), m);
-#else
-        SMBIODEBUG("M:%04x, P:%04x, U:%04x, T:%04x\n", rqp->sr_mid, 0, 0, 0);
-#endif
-        m_dumpm(m);
+        DTRACE_PROBE2(iod_sendrq,
+            (smb_rq_t *), rqp, (mblk_t *), top_m);
 
-        if (m != NULL) {
-                error = SMB_TRAN_SEND(vcp, m);
-                m = 0; /* consumed by SEND */
-        } else
-                error = ENOBUFS;
+        error = SMB_TRAN_SEND(vcp, top_m);
+        top_m = 0; /* consumed by SEND */
 
         rqp->sr_lerror = error;
         if (error == 0) {
                 SMBRQ_LOCK(rqp);
                 rqp->sr_flags |= SMBR_SENT;
                 rqp->sr_state = SMBRQ_SENT;
-                if (rqp->sr_flags & SMBR_SENDWAIT)
-                        cv_broadcast(&rqp->sr_cond);
                 SMBRQ_UNLOCK(rqp);
-                return (0);
+                return;
         }
         /*
-         * Check for fatal errors
+         * Transport send returned an error.
+         * Was it a fatal one?
          */
         if (SMB_TRAN_FATAL(vcp, error)) {
                 /*
                  * No further attempts should be made
                  */
+        fatal:
                 SMBSDEBUG("TRAN_SEND returned fatal error %d\n", error);
-                return (ENOTCONN);
+                smb_iod_rqprocessed(rqp, error, SMBR_RESTART);
+                return;
         }
-        if (error)
-                SMBSDEBUG("TRAN_SEND returned non-fatal error %d\n", error);
-
-#ifdef APPLE
-        /* If proc waiting on rqp was signaled... */
-        if (smb_rq_intr(rqp))
-                smb_iod_rqprocessed(rqp, EINTR, 0);
-#endif
-
-        return (0);
 }
 
+/*
+ * Receive one NetBIOS (or NBT over TCP) message.  If none have arrived,
+ * wait up to SMB_NBTIMO (15 sec.) for one to arrive, and then if still
+ * none have arrived, return ETIME.
+ */
 static int
-smb_iod_recv1(struct smb_vc *vcp, mblk_t **mpp)
+smb_iod_recvmsg(struct smb_vc *vcp, mblk_t **mpp)
 {
         mblk_t *m;
-        uchar_t *hp;
         int error;
 
 top:
         m = NULL;
         error = SMB_TRAN_RECV(vcp, &m);
         if (error == EAGAIN)
                 goto top;
         if (error)
                 return (error);
-        ASSERT(m);
+        ASSERT(m != NULL);
 
-        m = m_pullup(m, SMB_HDRLEN);
+        m = m_pullup(m, 4);
         if (m == NULL) {
                 return (ENOSR);
         }
 
-        /*
-         * Check the SMB header
-         */
-        hp = mtod(m, uchar_t *);
-        if (bcmp(hp, SMB_SIGNATURE, SMB_SIGLEN) != 0) {
-                m_freem(m);
-                return (EPROTO);
-        }
-
         *mpp = m;
         return (0);
 }
 
 /*
+ * How long should we keep around an unused VC (connection)?
+ * There's usually a good chance connections will be reused,
+ * so the default is to keep such connections for 5 min.
+ */
+#ifdef  DEBUG
+int smb_iod_idle_keep_time = 60;        /* seconds */
+#else
+int smb_iod_idle_keep_time = 300;       /* seconds */
+#endif
+
+/*
  * Process incoming packets
  *
- * This is the "reader" loop, run by the IOD thread
- * while in state SMBIOD_ST_VCACTIVE.  The loop now
- * simply blocks in the socket recv until either a
- * message arrives, or a disconnect.
+ * This is the "reader" loop, run by the IOD thread.  Normally we're in
+ * state SMBIOD_ST_VCACTIVE here, but during reconnect we're called in
+ * other states with poll==TRUE
  *
- * Any non-zero error means the IOD should terminate.
+ * A non-zero error return here causes the IOD work loop to terminate.
  */
 int
-smb_iod_recvall(struct smb_vc *vcp)
+smb_iod_recvall(struct smb_vc *vcp, boolean_t poll)
 {
-        struct smb_rq *rqp;
         mblk_t *m;
-        uchar_t *hp;
-        ushort_t mid;
         int error = 0;
-        int etime_count = 0; /* for "server not responding", etc. */
+        int etime_idle = 0;     /* How many 15 sec. "ticks" idle. */
+        int etime_count = 0;    /* ... and when we have requests. */
 
         for (;;) {
                 /*
                  * Check whether someone "killed" this VC,
                  * or is asking the IOD to terminate.
                  */
-
-                if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
-                        SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
-                        error = 0;
-                        break;
-                }
-
                 if (vcp->iod_flags & SMBIOD_SHUTDOWN) {
                         SMBIODEBUG("SHUTDOWN set\n");
                         /* This IOD thread will terminate. */
                         SMB_VC_LOCK(vcp);
                         smb_iod_newstate(vcp, SMBIOD_ST_DEAD);

@@ -386,115 +458,235 @@
                         error = EINTR;
                         break;
                 }
 
                 m = NULL;
-                error = smb_iod_recv1(vcp, &m);
+                error = smb_iod_recvmsg(vcp, &m);
 
+                /*
+                 * Internal requests (reconnecting) call this in a loop
+                 * (with poll==TRUE) until the request completes.
+                 */
+                if (error == ETIME && poll)
+                        break;
+
                 if (error == ETIME &&
                     vcp->iod_rqlist.tqh_first != NULL) {
+
                         /*
-                         * Nothing received for 15 seconds and
-                         * we have requests in the queue.
+                         * Nothing received and requests waiting.
+                         * Increment etime_count.  If we were idle,
+                         * skip the 1st tick, because we started
+                         * waiting before there were any requests.
                          */
+                        if (etime_idle != 0) {
+                                etime_idle = 0;
+                        } else if (etime_count < INT16_MAX) {
                         etime_count++;
+                        }
 
                         /*
-                         * Once, at 15 sec. notify callbacks
-                         * and print the warning message.
+                         * ETIME and requests in the queue.
+                         * The first time (at 15 sec.)
+                         * Log an error (just once).
                          */
-                        if (etime_count == 1) {
-                                /* Was: smb_iod_notify_down(vcp); */
-                                if (fscb && fscb->fscb_down)
-                                        smb_vc_walkshares(vcp,
-                                            fscb->fscb_down);
+                        if (etime_count > 0 &&
+                            vcp->iod_noresp == B_FALSE) {
+                                vcp->iod_noresp = B_TRUE;
                                 zprintf(vcp->vc_zoneid,
                                     "SMB server %s not responding\n",
                                     vcp->vc_srvname);
                         }
-
                         /*
-                         * At 30 sec. try sending an echo, and then
-                         * once a minute thereafter.
+                         * At 30 sec. try sending an echo, which
+                         * should cause some response.
                          */
-                        if ((etime_count & 3) == 2) {
-                                (void) smb_iod_send_echo(vcp);
+                        if (etime_count == 2) {
+                                SMBIODEBUG("send echo\n");
+                                (void) smb_iod_send_echo(vcp, CRED());
                         }
-
+                        /*
+                         * At 45 sec. give up on the connection
+                         * and try to reconnect.
+                         */
+                        if (etime_count == 3) {
+                                SMB_VC_LOCK(vcp);
+                                smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT);
+                                SMB_VC_UNLOCK(vcp);
+                                SMB_TRAN_DISCONNECT(vcp);
+                                break;
+                        }
                         continue;
-                } /* ETIME && requests in queue */
+                } /* ETIME and requests in the queue */
 
                 if (error == ETIME) {
                         /*
-                         * If the IOD thread holds the last reference
-                         * to this VC, let the IOD thread terminate.
+                         * Nothing received and no active requests.
+                         *
+                         * If we've received nothing from the server for
+                         * smb_iod_idle_keep_time seconds, and the IOD
+                         * thread holds the last reference to this VC,
+                         * move to state IDLE and drop the TCP session.
+                         * The IDLE handler will destroy the VC unless
+                         * vc_state goes to RECONNECT before then.
                          */
-                        if (vcp->vc_co.co_usecount > 1)
+                        etime_count = 0;
+                        if (etime_idle < INT16_MAX)
+                                etime_idle++;
+                        if ((etime_idle * SMB_NBTIMO) <
+                            smb_iod_idle_keep_time)
                                 continue;
                         SMB_VC_LOCK(vcp);
                         if (vcp->vc_co.co_usecount == 1) {
-                                smb_iod_newstate(vcp, SMBIOD_ST_DEAD);
+                                smb_iod_newstate(vcp, SMBIOD_ST_IDLE);
                                 SMB_VC_UNLOCK(vcp);
+                                SMBIODEBUG("logoff & disconnect\n");
+                                (void) smb_iod_logoff(vcp, CRED());
+                                SMB_TRAN_DISCONNECT(vcp);
                                 error = 0;
                                 break;
                         }
                         SMB_VC_UNLOCK(vcp);
                         continue;
                 } /* error == ETIME */
 
                 if (error) {
                         /*
-                         * The recv. above returned some error
-                         * we can't continue from i.e. ENOTCONN.
-                         * It's dangerous to continue here.
-                         * (possible infinite loop!)
-                         *
-                         * If we have requests enqueued, next
-                         * state is reconnecting, else idle.
+                         * The recv above returned an error indicating
+                         * that our TCP session is no longer usable.
+                         * Disconnect the session and get ready to
+                         * reconnect.  If we have pending requests,
+                         * move to state reconnect immediately;
+                         * otherwise move to state IDLE until a
+                         * request is issued on this VC.
                          */
-                        int state;
                         SMB_VC_LOCK(vcp);
-                        state = (vcp->iod_rqlist.tqh_first != NULL) ?
-                            SMBIOD_ST_RECONNECT : SMBIOD_ST_IDLE;
-                        smb_iod_newstate(vcp, state);
+                        if (vcp->iod_rqlist.tqh_first != NULL)
+                                smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT);
+                        else
+                                smb_iod_newstate(vcp, SMBIOD_ST_IDLE);
                         cv_broadcast(&vcp->vc_statechg);
                         SMB_VC_UNLOCK(vcp);
-                        error = 0;
+                        SMB_TRAN_DISCONNECT(vcp);
                         break;
                 }
 
                 /*
                  * Received something.  Yea!
                  */
-                if (etime_count) {
                         etime_count = 0;
+                etime_idle = 0;
 
+                /*
+                 * If we just completed a reconnect after logging
+                 * "SMB server %s not responding" then log OK now.
+                 */
+                if (vcp->iod_noresp) {
+                        vcp->iod_noresp = B_FALSE;
                         zprintf(vcp->vc_zoneid, "SMB server %s OK\n",
                             vcp->vc_srvname);
+                }
 
-                        /* Was: smb_iod_notify_up(vcp); */
-                        if (fscb && fscb->fscb_up)
-                                smb_vc_walkshares(vcp, fscb->fscb_up);
+                if ((vcp->vc_flags & SMBV_SMB2) != 0) {
+                        error = smb2_iod_process(vcp, m);
+                } else {
+                        error = smb1_iod_process(vcp, m);
                 }
 
                 /*
-                 * Have an SMB packet.  The SMB header was
-                 * checked in smb_iod_recv1().
-                 * Find the request...
+                 * Reconnect calls this in a loop with poll=TRUE
+                 * We've received a response, so break now.
                  */
-                hp = mtod(m, uchar_t *);
-                /*LINTED*/
-                mid = letohs(SMB_HDRMID(hp));
-                SMBIODEBUG("mid %04x\n", (uint_t)mid);
+                if (poll) {
+                        error = 0;
+                        break;
+                }
+        }
 
+        return (error);
+}
+
+/*
+ * Have what should be an SMB1 reply.  Check and parse the header,
+ * then use the message ID to find the request this belongs to and
+ * post it on that request.
+ *
+ * Returns an error if the reader should give up.
+ * To be safe, error if we read garbage.
+ */
+static int
+smb1_iod_process(smb_vc_t *vcp, mblk_t *m)
+{
+        struct mdchain md;
+        struct smb_rq *rqp;
+        uint8_t cmd, sig[4];
+        uint16_t mid;
+        int err, skip;
+
+        m = m_pullup(m, SMB_HDRLEN);
+        if (m == NULL)
+                return (ENOMEM);
+
+        /*
+         * Note: Intentionally do NOT md_done(&md)
+         * because that would free the message and
+         * we just want to peek here.
+         */
+        md_initm(&md, m);
+
+        /*
+         * Check the SMB header version and get the MID.
+         *
+         * The header version should be SMB1 except when we're
+         * doing SMB1-to-SMB2 negotiation, in which case we may
+         * see an SMB2 header with message ID=0 (only allowed in
+         * vc_state == SMBIOD_ST_CONNECTED -- negotiationg).
+         */
+        err = md_get_mem(&md, sig, 4, MB_MSYSTEM);
+        if (err)
+                return (err);
+        if (sig[1] != 'S' || sig[2] != 'M' || sig[3] != 'B') {
+                goto bad_hdr;
+        }
+        switch (sig[0]) {
+        case SMB_HDR_V1:        /* SMB1 */
+                md_get_uint8(&md, &cmd);
+                /* Skip to and get the MID. At offset 5 now. */
+                skip = SMB_HDR_OFF_MID - 5;
+                md_get_mem(&md, NULL, skip, MB_MSYSTEM);
+                err = md_get_uint16le(&md, &mid);
+                if (err)
+                        return (err);
+                break;
+        case SMB_HDR_V2:        /* SMB2+ */
+                if (vcp->vc_state == SMBIOD_ST_CONNECTED) {
+                        /*
+                         * No need to look, can only be
+                         * MID=0, cmd=negotiate
+                         */
+                        cmd = SMB_COM_NEGOTIATE;
+                        mid = 0;
+                        break;
+                }
+                /* FALLTHROUGH */
+        bad_hdr:
+        default:
+                SMBIODEBUG("Bad SMB hdr\n");
+                m_freem(m);
+                return (EPROTO);
+        }
+
+        /*
+         * Find the reqeuest and post the reply
+         */
                 rw_enter(&vcp->iod_rqlock, RW_READER);
                 TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
 
                         if (rqp->sr_mid != mid)
                                 continue;
 
-                        DTRACE_PROBE2(smb_iod_recvrq,
+                DTRACE_PROBE2(iod_post_reply,
                             (smb_rq_t *), rqp, (mblk_t *), m);
                         m_dumpm(m);
 
                         SMBRQ_LOCK(rqp);
                         if (rqp->sr_rp.md_top == NULL) {

@@ -502,34 +694,186 @@
                         } else {
                                 if (rqp->sr_flags & SMBR_MULTIPACKET) {
                                         md_append_record(&rqp->sr_rp, m);
                                 } else {
                                         SMBRQ_UNLOCK(rqp);
-                                        SMBSDEBUG("duplicate response %d "
-                                            "(ignored)\n", mid);
+                                rqp = NULL;
                                         break;
                                 }
                         }
                         smb_iod_rqprocessed_LH(rqp, 0, 0);
                         SMBRQ_UNLOCK(rqp);
                         break;
                 }
+        rw_exit(&vcp->iod_rqlock);
 
                 if (rqp == NULL) {
-                        int cmd = SMB_HDRCMD(hp);
+                if (cmd != SMB_COM_ECHO) {
+                        SMBSDEBUG("drop resp: MID 0x%04x\n", (uint_t)mid);
+                }
+                m_freem(m);
+                /*
+                 * Keep going.  It's possible this reply came
+                 * after the request timed out and went away.
+                 */
+        }
+        return (0);
+}
 
-                        if (cmd != SMB_COM_ECHO)
-                                SMBSDEBUG("drop resp: mid %d, cmd %d\n",
-                                    (uint_t)mid, cmd);
-/*                      smb_printrqlist(vcp); */
+/*
+ * Have what should be an SMB2 reply.  Check and parse the header,
+ * then use the message ID to find the request this belongs to and
+ * post it on that request.
+ *
+ * We also want to apply any credit grant in this reply now,
+ * rather than waiting for the owner to wake up.
+ */
+static int
+smb2_iod_process(smb_vc_t *vcp, mblk_t *m)
+{
+        struct mdchain md;
+        struct smb_rq *rqp;
+        uint8_t sig[4];
+        mblk_t *next_m = NULL;
+        uint64_t message_id, async_id;
+        uint32_t flags, next_cmd_off, status;
+        uint16_t command, credits_granted;
+        int err;
+
+top:
+        m = m_pullup(m, SMB2_HDRLEN);
+        if (m == NULL)
+                return (ENOMEM);
+
+        /*
+         * Note: Intentionally do NOT md_done(&md)
+         * because that would free the message and
+         * we just want to peek here.
+         */
+        md_initm(&md, m);
+
+        /*
+         * Check the SMB header.  Must be SMB2
+         * (and later, could be SMB3 encrypted)
+         */
+        err = md_get_mem(&md, sig, 4, MB_MSYSTEM);
+        if (err)
+                return (err);
+        if (sig[1] != 'S' || sig[2] != 'M' || sig[3] != 'B') {
+                goto bad_hdr;
+        }
+        switch (sig[0]) {
+        case SMB_HDR_V2:
+                break;
+        case SMB_HDR_V3E:
+                /*
+                 * Todo: If encryption enabled, decrypt the message
+                 * and restart processing on the cleartext.
+                 */
+                /* FALLTHROUGH */
+        bad_hdr:
+        default:
+                SMBIODEBUG("Bad SMB2 hdr\n");
                         m_freem(m);
+                return (EPROTO);
                 }
+
+        /*
+         * Parse the rest of the SMB2 header,
+         * skipping what we don't need.
+         */
+        md_get_uint32le(&md, NULL);     /* length, credit_charge */
+        md_get_uint32le(&md, &status);
+        md_get_uint16le(&md, &command);
+        md_get_uint16le(&md, &credits_granted);
+        md_get_uint32le(&md, &flags);
+        md_get_uint32le(&md, &next_cmd_off);
+        md_get_uint64le(&md, &message_id);
+        if (flags & SMB2_FLAGS_ASYNC_COMMAND) {
+                md_get_uint64le(&md, &async_id);
+        } else {
+                /* PID, TID (not needed) */
+                async_id = 0;
+        }
+
+        /*
+         * If this is a compound reply, split it.
+         * Next must be 8-byte aligned.
+         */
+        if (next_cmd_off != 0) {
+                if ((next_cmd_off & 7) != 0)
+                        SMBIODEBUG("Misaligned next cmd\n");
+                else
+                        next_m = m_split(m, next_cmd_off, 1);
+        }
+
+        /*
+         * Apply the credit grant
+         */
+        rw_enter(&vcp->iod_rqlock, RW_WRITER);
+        vcp->vc2_limit_message_id += credits_granted;
+
+        /*
+         * Find the reqeuest and post the reply
+         */
+        rw_downgrade(&vcp->iod_rqlock);
+        TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
+
+                if (rqp->sr2_messageid != message_id)
+                        continue;
+
+                DTRACE_PROBE2(iod_post_reply,
+                    (smb_rq_t *), rqp, (mblk_t *), m);
+                m_dumpm(m);
+
+                /*
+                 * If this is an interim response, just save the
+                 * async ID but don't wakup the request.
+                 * Don't need SMBRQ_LOCK for this.
+                 */
+                if (status == NT_STATUS_PENDING && async_id != 0) {
+                        rqp->sr2_rspasyncid = async_id;
+                        m_freem(m);
+                        break;
+                }
+
+                SMBRQ_LOCK(rqp);
+                if (rqp->sr_rp.md_top == NULL) {
+                        md_initm(&rqp->sr_rp, m);
+                } else {
+                        SMBRQ_UNLOCK(rqp);
+                        rqp = NULL;
+                        break;
+                }
+                smb_iod_rqprocessed_LH(rqp, 0, 0);
+                SMBRQ_UNLOCK(rqp);
+                break;
+        }
                 rw_exit(&vcp->iod_rqlock);
 
+        if (rqp == NULL) {
+                if (command != SMB2_ECHO) {
+                        SMBSDEBUG("drop resp: MID %lld\n",
+                            (long long)message_id);
         }
+                m_freem(m);
+                /*
+                 * Keep going.  It's possible this reply came
+                 * after the request timed out and went away.
+                 */
+        }
 
-        return (error);
+        /*
+         * If we split a compound reply, continue with the
+         * next part of the compound.
+         */
+        if (next_m != NULL) {
+                m = next_m;
+                goto top;
+        }
+
+        return (0);
 }
 
 /*
  * The IOD receiver thread has requests pending and
  * has not received anything in a while.  Try to

@@ -539,130 +883,260 @@
  * Using tmo=SMBNOREPLYWAIT in the request
  * so smb_rq_reply will skip smb_iod_waitrq.
  * The smb_smb_echo call uses SMBR_INTERNAL
  * to avoid calling smb_iod_sendall().
  */
-int
-smb_iod_send_echo(smb_vc_t *vcp)
+static int
+smb_iod_send_echo(smb_vc_t *vcp, cred_t *cr)
 {
         smb_cred_t scred;
-        int err;
+        int err, tmo = SMBNOREPLYWAIT;
 
-        smb_credinit(&scred, NULL);
-        err = smb_smb_echo(vcp, &scred, SMBNOREPLYWAIT);
+        ASSERT(vcp->iod_thr == curthread);
+
+        smb_credinit(&scred, cr);
+        if ((vcp->vc_flags & SMBV_SMB2) != 0) {
+                err = smb2_smb_echo(vcp, &scred, tmo);
+        } else {
+                err = smb_smb_echo(vcp, &scred, tmo);
+        }
         smb_credrele(&scred);
         return (err);
 }
 
 /*
- * The IOD thread is now just a "reader",
- * so no more smb_iod_request().  Yea!
+ * Helper for smb1_iod_addrq, smb2_iod_addrq
+ * Returns zero if interrupted, else 1.
  */
+static int
+smb_iod_muxwait(smb_vc_t *vcp, boolean_t sig_ok)
+{
+        int rc;
 
+        SMB_VC_LOCK(vcp);
+        vcp->iod_muxwant++;
+        if (sig_ok) {
+                rc = cv_wait_sig(&vcp->iod_muxwait, &vcp->vc_lock);
+        } else {
+                cv_wait(&vcp->iod_muxwait, &vcp->vc_lock);
+                rc = 1;
+        }
+        vcp->iod_muxwant--;
+        SMB_VC_UNLOCK(vcp);
+
+        return (rc);
+}
+
 /*
- * Place request in the queue, and send it now if possible.
+ * Place request in the queue, and send it.
  * Called with no locks held.
+ *
+ * Called for SMB1 only
+ *
+ * The logic for how we limit active requests differs between
+ * SMB1 and SMB2.  With SMB1 it's a simple counter ioc_muxcnt.
  */
 int
-smb_iod_addrq(struct smb_rq *rqp)
+smb1_iod_addrq(struct smb_rq *rqp)
 {
         struct smb_vc *vcp = rqp->sr_vc;
-        int error, save_newrq;
+        uint16_t need;
+        boolean_t sig_ok =
+            (rqp->sr_flags & SMBR_NOINTR_SEND) == 0;
 
         ASSERT(rqp->sr_cred);
+        ASSERT((vcp->vc_flags & SMBV_SMB2) == 0);
 
+        rqp->sr_owner = curthread;
+
+        rw_enter(&vcp->iod_rqlock, RW_WRITER);
+
+recheck:
         /*
-         * State should be correct after the check in
-         * smb_rq_enqueue(), but we dropped locks...
+         * Internal requests can be added in any state,
+         * but normal requests only in state active.
          */
-        if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
+        if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
+            vcp->vc_state != SMBIOD_ST_VCACTIVE) {
                 SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
+                rw_exit(&vcp->iod_rqlock);
                 return (ENOTCONN);
         }
 
         /*
-         * Requests from the IOD itself are marked _INTERNAL,
-         * and get some special treatment to avoid blocking
-         * the reader thread (so we don't deadlock).
-         * The request is not yet on the queue, so we can
-         * modify it's state here without locks.
-         * Only thing using this now is ECHO.
+         * If we're at the limit of active requests, block until
+         * enough requests complete so we can make ours active.
+         * Wakeup in smb_iod_removerq().
+         *
+         * Normal callers leave one slot free, so internal
+         * callers can have the last slot if needed.
          */
-        rqp->sr_owner = curthread;
-        if (rqp->sr_owner == vcp->iod_thr) {
-                rqp->sr_flags |= SMBR_INTERNAL;
-
-                /*
-                 * This is a request from the IOD thread.
-                 * Always send directly from this thread.
-                 * Note lock order: iod_rqlist, vc_sendlock
-                 */
+        need = 1;
+        if ((rqp->sr_flags & SMBR_INTERNAL) == 0)
+                need++;
+        if ((vcp->iod_muxcnt + need) > vcp->vc_maxmux) {
+                rw_exit(&vcp->iod_rqlock);
+                if (rqp->sr_flags & SMBR_INTERNAL)
+                        return (EBUSY);
+                if (smb_iod_muxwait(vcp, sig_ok) == 0)
+                        return (EINTR);
                 rw_enter(&vcp->iod_rqlock, RW_WRITER);
-                TAILQ_INSERT_HEAD(&vcp->iod_rqlist, rqp, sr_link);
-                rw_downgrade(&vcp->iod_rqlock);
+                goto recheck;
+        }
 
                 /*
-                 * Note: iod_sendrq expects vc_sendlock,
-                 * so take that here, but carefully:
-                 * Never block the IOD thread here.
+         * Add this request to the active list and send it.
+         * For SMB2 we may have a sequence of compounded
+         * requests, in which case we must add them all.
+         * They're sent as a compound in smb2_iod_sendrq.
                  */
-                if (sema_tryp(&vcp->vc_sendlock) == 0) {
-                        SMBIODEBUG("sendlock busy\n");
-                        error = EAGAIN;
-                } else {
-                        /* Have vc_sendlock */
-                        error = smb_iod_sendrq(rqp);
-                        sema_v(&vcp->vc_sendlock);
+        rqp->sr_mid = vcp->vc_next_mid++;
+        /* If signing, set the signing sequence numbers. */
+        if (vcp->vc_mackey != NULL && (rqp->sr_rqflags2 &
+            SMB_FLAGS2_SECURITY_SIGNATURE) != 0) {
+                rqp->sr_seqno = vcp->vc_next_seq++;
+                rqp->sr_rseqno = vcp->vc_next_seq++;
                 }
+        vcp->iod_muxcnt++;
+        TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link);
+        smb1_iod_sendrq(rqp);
 
                 rw_exit(&vcp->iod_rqlock);
+        return (0);
+}
 
+/*
+ * Place request in the queue, and send it.
+ * Called with no locks held.
+ *
+ * Called for SMB2 only.
+ *
+ * With SMB2 we have a range of valid message IDs, and we may
+ * only send requests when we can assign a message ID within
+ * the valid range.  We may need to wait here for some active
+ * request to finish (and update vc2_limit_message_id) before
+ * we can get message IDs for our new request(s).  Another
+ * difference is that the request sequence we're waiting to
+ * add here may require multipe message IDs, either due to
+ * either compounding or multi-credit requests.  Therefore
+ * we need to wait for the availibility of how ever many
+ * message IDs are required by our request sequence.
+ */
+int
+smb2_iod_addrq(struct smb_rq *rqp)
+{
+        struct smb_vc *vcp = rqp->sr_vc;
+        struct smb_rq *c_rqp;   /* compound req */
+        uint16_t charge;
+        boolean_t sig_ok =
+            (rqp->sr_flags & SMBR_NOINTR_SEND) == 0;
+
+        ASSERT(rqp->sr_cred != NULL);
+        ASSERT((vcp->vc_flags & SMBV_SMB2) != 0);
+
                 /*
-                 * In the non-error case, _removerq
-                 * is done by either smb_rq_reply
-                 * or smb_iod_waitrq.
+         * Figure out the credit charges
+         * No multi-credit messages yet.
                  */
-                if (error)
-                        smb_iod_removerq(rqp);
+        rqp->sr2_totalcreditcharge = rqp->sr2_creditcharge;
+        c_rqp = rqp->sr2_compound_next;
+        while (c_rqp != NULL) {
+                rqp->sr2_totalcreditcharge += c_rqp->sr2_creditcharge;
+                c_rqp = c_rqp->sr2_compound_next;
+        }
 
-                return (error);
+        /*
+         * Internal request must not be compounded
+         * and should use exactly one credit.
+         */
+        if (rqp->sr_flags & SMBR_INTERNAL) {
+                if (rqp->sr2_compound_next != NULL) {
+                        ASSERT(0);
+                        return (EINVAL);
         }
+        }
 
+        rqp->sr_owner = curthread;
+
         rw_enter(&vcp->iod_rqlock, RW_WRITER);
 
-        TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link);
-        /* iod_rqlock/WRITER protects iod_newrq */
-        save_newrq = vcp->iod_newrq;
-        vcp->iod_newrq++;
+recheck:
+        /*
+         * Internal requests can be added in any state,
+         * but normal requests only in state active.
+         */
+        if ((rqp->sr_flags & SMBR_INTERNAL) == 0 &&
+            vcp->vc_state != SMBIOD_ST_VCACTIVE) {
+                SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);
+                rw_exit(&vcp->iod_rqlock);
+                return (ENOTCONN);
+        }
 
+        /*
+         * If we're at the limit of active requests, block until
+         * enough requests complete so we can make ours active.
+         * Wakeup in smb_iod_removerq().
+         *
+         * Normal callers leave one slot free, so internal
+         * callers can have the last slot if needed.
+         */
+        charge = rqp->sr2_totalcreditcharge;
+        if ((rqp->sr_flags & SMBR_INTERNAL) == 0)
+                charge++;
+        if ((vcp->vc2_next_message_id + charge) >
+            vcp->vc2_limit_message_id) {
         rw_exit(&vcp->iod_rqlock);
+                if (rqp->sr_flags & SMBR_INTERNAL)
+                        return (EBUSY);
+                if (smb_iod_muxwait(vcp, sig_ok) == 0)
+                        return (EINTR);
+                rw_enter(&vcp->iod_rqlock, RW_WRITER);
+                goto recheck;
+        }
 
         /*
-         * Now send any requests that need to be sent,
-         * including the one we just put on the list.
-         * Only the thread that found iod_newrq==0
-         * needs to run the send loop.
+         * Add this request to the active list and send it.
+         * For SMB2 we may have a sequence of compounded
+         * requests, in which case we must add them all.
+         * They're sent as a compound in smb2_iod_sendrq.
          */
-        if (save_newrq == 0)
-                smb_iod_sendall(vcp);
 
+        rqp->sr2_messageid = vcp->vc2_next_message_id;
+        vcp->vc2_next_message_id += rqp->sr2_creditcharge;
+        TAILQ_INSERT_TAIL(&vcp->iod_rqlist, rqp, sr_link);
+
+        c_rqp = rqp->sr2_compound_next;
+        while (c_rqp != NULL) {
+                c_rqp->sr2_messageid = vcp->vc2_next_message_id;
+                vcp->vc2_next_message_id += c_rqp->sr2_creditcharge;
+                TAILQ_INSERT_TAIL(&vcp->iod_rqlist, c_rqp, sr_link);
+                c_rqp = c_rqp->sr2_compound_next;
+        }
+        smb2_iod_sendrq(rqp);
+
+        rw_exit(&vcp->iod_rqlock);
         return (0);
 }
 
 /*
  * Mark an SMBR_MULTIPACKET request as
  * needing another send.  Similar to the
- * "normal" part of smb_iod_addrq.
+ * "normal" part of smb1_iod_addrq.
+ * Only used by SMB1
  */
 int
-smb_iod_multirq(struct smb_rq *rqp)
+smb1_iod_multirq(struct smb_rq *rqp)
 {
         struct smb_vc *vcp = rqp->sr_vc;
-        int save_newrq;
 
         ASSERT(rqp->sr_flags & SMBR_MULTIPACKET);
 
+        if (vcp->vc_flags & SMBV_SMB2) {
+                ASSERT("!SMB2?");
+                return (EINVAL);
+        }
+
         if (rqp->sr_flags & SMBR_INTERNAL)
                 return (EINVAL);
 
         if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
                 SMBIODEBUG("bad vc_state=%d\n", vcp->vc_state);

@@ -671,68 +1145,89 @@
 
         rw_enter(&vcp->iod_rqlock, RW_WRITER);
 
         /* Already on iod_rqlist, just reset state. */
         rqp->sr_state = SMBRQ_NOTSENT;
+        smb1_iod_sendrq(rqp);
 
-        /* iod_rqlock/WRITER protects iod_newrq */
-        save_newrq = vcp->iod_newrq;
-        vcp->iod_newrq++;
-
         rw_exit(&vcp->iod_rqlock);
 
-        /*
-         * Now send any requests that need to be sent,
-         * including the one we just marked NOTSENT.
-         * Only the thread that found iod_newrq==0
-         * needs to run the send loop.
-         */
-        if (save_newrq == 0)
-                smb_iod_sendall(vcp);
-
         return (0);
 }
 
-
+/*
+ * Remove a request from the active list, and
+ * wake up requests waiting to go active.
+ *
+ * Shared by SMB1 + SMB2
+ *
+ * The logic for how we limit active requests differs between
+ * SMB1 and SMB2.  With SMB1 it's a simple counter ioc_muxcnt.
+ * With SMB2 we have a range of valid message IDs, and when we
+ * retire the oldest request we need to keep track of what is
+ * now the oldest message ID.  In both cases, after we take a
+ * request out of the list here, we should be able to wake up
+ * a request waiting to get in the active list.
+ */
 void
 smb_iod_removerq(struct smb_rq *rqp)
 {
+        struct smb_rq *rqp2;
         struct smb_vc *vcp = rqp->sr_vc;
+        boolean_t was_head = B_FALSE;
 
         rw_enter(&vcp->iod_rqlock, RW_WRITER);
+
 #ifdef QUEUEDEBUG
         /*
          * Make sure we have not already removed it.
          * See sys/queue.h QUEUEDEBUG_TAILQ_POSTREMOVE
          * XXX: Don't like the constant 1 here...
          */
         ASSERT(rqp->sr_link.tqe_next != (void *)1L);
 #endif
+
+        if (TAILQ_FIRST(&vcp->iod_rqlist) == rqp)
+                was_head = B_TRUE;
         TAILQ_REMOVE(&vcp->iod_rqlist, rqp, sr_link);
+        if (vcp->vc_flags & SMBV_SMB2) {
+                rqp2 = TAILQ_FIRST(&vcp->iod_rqlist);
+                if (was_head && rqp2 != NULL) {
+                        /* Do we still need this? */
+                        vcp->vc2_oldest_message_id =
+                            rqp2->sr2_messageid;
+                }
+        } else {
+                ASSERT(vcp->iod_muxcnt > 0);
+                vcp->iod_muxcnt--;
+        }
+
         rw_exit(&vcp->iod_rqlock);
+
+        /*
+         * If there are requests waiting for "mux" slots,
+         * wake one.
+         */
+        SMB_VC_LOCK(vcp);
+        if (vcp->iod_muxwant != 0)
+                cv_signal(&vcp->iod_muxwait);
+        SMB_VC_UNLOCK(vcp);
 }
 
-
-
 /*
  * Wait for a request to complete.
- *
- * For normal requests, we need to deal with
- * ioc_muxcnt dropping below vc_maxmux by
- * making arrangements to send more...
  */
 int
 smb_iod_waitrq(struct smb_rq *rqp)
 {
         struct smb_vc *vcp = rqp->sr_vc;
         clock_t tr, tmo1, tmo2;
-        int error, rc;
+        int error;
 
         if (rqp->sr_flags & SMBR_INTERNAL) {
-                ASSERT((rqp->sr_flags & SMBR_MULTIPACKET) == 0);
-                smb_iod_removerq(rqp);
-                return (EAGAIN);
+                /* XXX - Do we ever take this path now? */
+                return (smb_iod_waitrq_int(rqp));
         }
 
         /*
          * Make sure this is NOT the IOD thread,
          * or the wait below will stop the reader.

@@ -740,39 +1235,10 @@
         ASSERT(curthread != vcp->iod_thr);
 
         SMBRQ_LOCK(rqp);
 
         /*
-         * First, wait for the request to be sent.  Normally the send
-         * has already happened by the time we get here.  However, if
-         * we have more than maxmux entries in the request list, our
-         * request may not be sent until other requests complete.
-         * The wait in this case is due to local I/O demands, so
-         * we don't want the server response timeout to apply.
-         *
-         * If a request is allowed to interrupt this wait, then the
-         * request is cancelled and never sent OTW.  Some kinds of
-         * requests should never be cancelled (i.e. close) and those
-         * are marked SMBR_NOINTR_SEND so they either go eventually,
-         * or a connection close will terminate them with ENOTCONN.
-         */
-        while (rqp->sr_state == SMBRQ_NOTSENT) {
-                rqp->sr_flags |= SMBR_SENDWAIT;
-                if (rqp->sr_flags & SMBR_NOINTR_SEND) {
-                        cv_wait(&rqp->sr_cond, &rqp->sr_lock);
-                        rc = 1;
-                } else
-                        rc = cv_wait_sig(&rqp->sr_cond, &rqp->sr_lock);
-                rqp->sr_flags &= ~SMBR_SENDWAIT;
-                if (rc == 0) {
-                        SMBIODEBUG("EINTR in sendwait, rqp=%p\n", rqp);
-                        error = EINTR;
-                        goto out;
-                }
-        }
-
-        /*
          * The request has been sent.  Now wait for the response,
          * with the timeout specified for this request.
          * Compute all the deadlines now, so we effectively
          * start the timer(s) after the request is sent.
          */

@@ -801,21 +1267,12 @@
                 if (tr == 0) {
                         error = EINTR;
                         goto out;
                 }
                 if (tr < 0) {
-#ifdef DTRACE_PROBE
                         DTRACE_PROBE1(smb_iod_waitrq1,
                             (smb_rq_t *), rqp);
-#endif
-#ifdef NOT_YET
-                        /* Want this to go ONLY to the user. */
-                        uprintf("SMB server %s has not responded"
-                            " to request %d after %d seconds..."
-                            " (still waiting).\n", vcp->vc_srvname,
-                            rqp->sr_mid, smb_timo_notice);
-#endif
                 }
         }
 
         /*
          * Keep waiting until tmo2 is expired.

@@ -830,21 +1287,12 @@
                 if (tr == 0) {
                         error = EINTR;
                         goto out;
                 }
                 if (tr < 0) {
-#ifdef DTRACE_PROBE
                         DTRACE_PROBE1(smb_iod_waitrq2,
                             (smb_rq_t *), rqp);
-#endif
-#ifdef NOT_YET
-                        /* Want this to go ONLY to the user. */
-                        uprintf("SMB server %s has not responded"
-                            " to request %d after %d seconds..."
-                            " (giving up).\n", vcp->vc_srvname,
-                            rqp->sr_mid, rqp->sr_timo);
-#endif
                         error = ETIME;
                         goto out;
                 }
                 /* got wakeup */
         }

@@ -859,18 +1307,39 @@
          * They may need additional responses.
          */
         if ((rqp->sr_flags & SMBR_MULTIPACKET) == 0)
                 smb_iod_removerq(rqp);
 
-        /*
-         * Some request has been completed.
-         * If we reached the mux limit,
-         * re-run the send loop...
+        return (error);
+}
+
+/*
+ * Internal variant of smb_iod_waitrq(), for use in
+ * requests run by the IOD (reader) thread itself.
+ * Block only long enough to receive one reply.
          */
-        if (vcp->iod_muxfull)
-                smb_iod_sendall(vcp);
+int
+smb_iod_waitrq_int(struct smb_rq *rqp)
+{
+        struct smb_vc *vcp = rqp->sr_vc;
+        int timeleft = rqp->sr_timo;
+        int error;
 
+        ASSERT((rqp->sr_flags & SMBR_MULTIPACKET) == 0);
+again:
+        error = smb_iod_recvall(vcp, B_TRUE);
+        if (error == ETIME) {
+                /* We waited SMB_NBTIMO sec. */
+                timeleft -= SMB_NBTIMO;
+                if (timeleft > 0)
+                        goto again;
+        }
+
+        smb_iod_removerq(rqp);
+        if (rqp->sr_state != SMBRQ_NOTIFIED)
+                error = ETIME;
+
         return (error);
 }
 
 /*
  * Shutdown all outstanding I/O requests on the specified share with

@@ -895,107 +1364,364 @@
         }
         rw_exit(&vcp->iod_rqlock);
 }
 
 /*
- * Send all requests that need sending.
- * Called from _addrq, _multirq, _waitrq
+ * Ioctl functions called by the user-level I/O Deamon (IOD)
+ * to bring up and service a connection to some SMB server.
  */
-void
-smb_iod_sendall(smb_vc_t *vcp)
+
+/*
+ * Handle ioctl SMBIOC_IOD_CONNECT
+ */
+int
+nsmb_iod_connect(struct smb_vc *vcp, cred_t *cr)
 {
-        struct smb_rq *rqp;
-        int error, muxcnt;
+        int err, val;
 
+        ASSERT(vcp->iod_thr == curthread);
+
+        if (vcp->vc_state != SMBIOD_ST_RECONNECT) {
+                cmn_err(CE_NOTE, "iod_connect: bad state %d", vcp->vc_state);
+                return (EINVAL);
+        }
+
         /*
-         * Clear "newrq" to make sure threads adding
-         * new requests will run this function again.
+         * Putting a TLI endpoint back in the right state for a new
+         * connection is a bit tricky.  In theory, this could be:
+         *      SMB_TRAN_DISCONNECT(vcp);
+         *      SMB_TRAN_UNBIND(vcp);
+         * but that method often results in TOUTSTATE errors.
+         * It's easier to just close it and open a new endpoint.
          */
-        rw_enter(&vcp->iod_rqlock, RW_WRITER);
-        vcp->iod_newrq = 0;
+        SMB_VC_LOCK(vcp);
+        if (vcp->vc_tdata)
+                SMB_TRAN_DONE(vcp);
+        err = SMB_TRAN_CREATE(vcp, cr);
+        SMB_VC_UNLOCK(vcp);
+        if (err != 0)
+                return (err);
 
         /*
-         * We only read iod_rqlist, so downgrade rwlock.
-         * This allows the IOD to handle responses while
-         * some requesting thread may be blocked in send.
+         * Set various options on this endpoint.
+         * Keep going in spite of errors.
          */
-        rw_downgrade(&vcp->iod_rqlock);
+        val = smb_tcpsndbuf;
+        err = SMB_TRAN_SETPARAM(vcp, SMBTP_SNDBUF, &val);
+        if (err != 0) {
+                cmn_err(CE_NOTE, "iod_connect: setopt SNDBUF, err=%d", err);
+        }
+        val = smb_tcprcvbuf;
+        err = SMB_TRAN_SETPARAM(vcp, SMBTP_RCVBUF, &val);
+        if (err != 0) {
+                cmn_err(CE_NOTE, "iod_connect: setopt RCVBUF, err=%d", err);
+        }
+        val = 1;
+        err = SMB_TRAN_SETPARAM(vcp, SMBTP_KEEPALIVE, &val);
+        if (err != 0) {
+                cmn_err(CE_NOTE, "iod_connect: setopt KEEPALIVE, err=%d", err);
+        }
+        val = 1;
+        err = SMB_TRAN_SETPARAM(vcp, SMBTP_TCP_NODELAY, &val);
+        if (err != 0) {
+                cmn_err(CE_NOTE, "iod_connect: setopt TCP_NODELAY, err=%d", err);
+        }
+        val = smb_connect_timeout * 1000;
+        err = SMB_TRAN_SETPARAM(vcp, SMBTP_TCP_CON_TMO, &val);
+        if (err != 0) {
+                cmn_err(CE_NOTE, "iod_connect: setopt TCP con tmo, err=%d", err);
+        }
 
         /*
-         * Serialize to prevent multiple senders.
-         * Note lock order: iod_rqlock, vc_sendlock
+         * Bind and connect
          */
-        sema_p(&vcp->vc_sendlock);
-
+        err = SMB_TRAN_BIND(vcp, NULL);
+        if (err != 0) {
+                cmn_err(CE_NOTE, "iod_connect: t_kbind: err=%d", err);
+                /* Continue on and try connect. */
+        }
+        err = SMB_TRAN_CONNECT(vcp, &vcp->vc_srvaddr.sa);
         /*
-         * Walk the list of requests and send when possible.
-         * We avoid having more than vc_maxmux requests
-         * outstanding to the server by traversing only
-         * vc_maxmux entries into this list.  Simple!
+         * No cmn_err here, as connect failures are normal, i.e.
+         * when a server has multiple addresses and only some are
+         * routed for us. (libsmbfs tries them all)
          */
-        ASSERT(vcp->vc_maxmux > 0);
-        error = muxcnt = 0;
-        TAILQ_FOREACH(rqp, &vcp->iod_rqlist, sr_link) {
+        if (err == 0) {
+                SMB_VC_LOCK(vcp);
+                smb_iod_newstate(vcp, SMBIOD_ST_CONNECTED);
+                SMB_VC_UNLOCK(vcp);
+        } /* else stay in state reconnect */
 
-                if (vcp->vc_state != SMBIOD_ST_VCACTIVE) {
-                        error = ENOTCONN; /* stop everything! */
-                        break;
+        return (err);
+}
+
+/*
+ * Handle ioctl SMBIOC_IOD_NEGOTIATE
+ * Do the whole SMB1/SMB2 negotiate
+ *
+ * This is where we send our first request to the server.
+ * If this is the first time we're talking to this server,
+ * (meaning not a reconnect) then we don't know whether
+ * the server supports SMB2, so we need to use the weird
+ * SMB1-to-SMB2 negotiation. That's where we send an SMB1
+ * negotiate including dialect "SMB 2.???" and if the
+ * server supports SMB2 we get an SMB2 reply -- Yes, an
+ * SMB2 reply to an SMB1 request.  A strange protocol...
+ *
+ * If on the other hand we already know the server supports
+ * SMB2 (because this is a reconnect) or if the client side
+ * has disabled SMB1 entirely, we'll skip the SMB1 part.
+ */
+int
+nsmb_iod_negotiate(struct smb_vc *vcp, cred_t *cr)
+{
+        struct smb_sopt *sv = &vcp->vc_sopt;
+        smb_cred_t scred;
+        int err = 0;
+
+        ASSERT(vcp->iod_thr == curthread);
+
+        smb_credinit(&scred, cr);
+
+        if (vcp->vc_state != SMBIOD_ST_CONNECTED) {
+                cmn_err(CE_NOTE, "iod_negotiate: bad state %d", vcp->vc_state);
+                err = EINVAL;
+                goto out;
                 }
 
-                if (rqp->sr_state == SMBRQ_NOTSENT) {
-                        error = smb_iod_sendrq(rqp);
-                        if (error)
-                                break;
+        if (vcp->vc_maxver == 0 || vcp->vc_minver > vcp->vc_maxver) {
+                err = EINVAL;
+                goto out;
                 }
 
-                if (++muxcnt == vcp->vc_maxmux) {
-                        SMBIODEBUG("muxcnt == vc_maxmux\n");
-                        break;
+        /*
+         * (Re)init negotiated values
+         */
+        bzero(sv, sizeof (*sv));
+        vcp->vc2_next_message_id = 0;
+        vcp->vc2_limit_message_id = 1;
+        vcp->vc2_session_id = 0;
+        vcp->vc_next_seq = 0;
+
+        /*
+         * If this was reconnect, get rid of the old MAC key
+         * and session key.
+         */
+        SMB_VC_LOCK(vcp);
+        if (vcp->vc_mackey != NULL) {
+                kmem_free(vcp->vc_mackey, vcp->vc_mackeylen);
+                vcp->vc_mackey = NULL;
+                vcp->vc_mackeylen = 0;
                 }
+        if (vcp->vc_ssnkey != NULL) {
+                kmem_free(vcp->vc_ssnkey, vcp->vc_ssnkeylen);
+                vcp->vc_ssnkey = NULL;
+                vcp->vc_ssnkeylen = 0;
+        }
+        SMB_VC_UNLOCK(vcp);
 
+        /*
+         * If this is not an SMB2 reconect (SMBV_SMB2 not set),
+         * and if SMB1 is enabled, do SMB1 neogotiate.  Then
+         * if either SMB1-to-SMB2 negotiate tells us we should
+         * switch to SMB2, or the local configuration has
+         * disabled SMB1, set the SMBV_SMB2 flag.
+         *
+         * Note that vc_maxver is handled in smb_smb_negotiate
+         * so we never get sv_proto == SMB_DIALECT_SMB2_FF when
+         * the local configuration disables SMB2, and therefore
+         * we won't set the SMBV_SMB2 flag.
+         */
+        if ((vcp->vc_flags & SMBV_SMB2) == 0) {
+                if (vcp->vc_minver < SMB2_DIALECT_BASE) {
+                        /*
+                         * SMB1 is enabled
+                         */
+                        err = smb_smb_negotiate(vcp, &scred);
+                        if (err != 0)
+                                goto out;
         }
+                /*
+                 * If SMB1-to-SMB2 negotiate told us we should
+                 * switch to SMB2, or if the local configuration
+                 * disables SMB1, set the SMB2 flag.
+                 */
+                if (sv->sv_proto == SMB_DIALECT_SMB2_FF ||
+                    vcp->vc_minver >= SMB2_DIALECT_BASE) {
+                        /*
+                         * Switch this VC to SMB2.
+                         */
+                        SMB_VC_LOCK(vcp);
+                        vcp->vc_flags |= SMBV_SMB2;
+                        SMB_VC_UNLOCK(vcp);
+                }
+        }
 
         /*
-         * If we have vc_maxmux requests outstanding,
-         * arrange for _waitrq to call _sendall as
-         * requests are completed.
+         * If this is an SMB2 reconnect (SMBV_SMB2 was set before this
+         * function was called), or SMB1-to-SMB2 negotiate indicated
+         * we should switch to SMB2, or we have SMB1 disabled (both
+         * cases set SMBV_SMB2 above), then do SMB2 negotiate.
          */
-        vcp->iod_muxfull =
-            (muxcnt < vcp->vc_maxmux) ? 0 : 1;
+        if ((vcp->vc_flags & SMBV_SMB2) != 0) {
+                err = smb2_smb_negotiate(vcp, &scred);
+        }
 
-        sema_v(&vcp->vc_sendlock);
-        rw_exit(&vcp->iod_rqlock);
+out:
+        if (err == 0) {
+                SMB_VC_LOCK(vcp);
+                smb_iod_newstate(vcp, SMBIOD_ST_NEGOTIATED);
+                SMB_VC_UNLOCK(vcp);
+        }
+        /*
+         * (else) leave state as it was.
+         * User-level will either close this handle (if connecting
+         * for the first time) or call rcfail and then try again.
+         */
+
+        smb_credrele(&scred);
+
+        return (err);
 }
 
+/*
+ * Handle ioctl SMBIOC_IOD_SSNSETUP
+ * Do either SMB1 or SMB2 session setup (one call/reply)
+ */
 int
-smb_iod_vc_work(struct smb_vc *vcp, cred_t *cr)
+nsmb_iod_ssnsetup(struct smb_vc *vcp, cred_t *cr)
 {
-        struct file *fp = NULL;
+        smb_cred_t scred;
+        int err;
+
+        ASSERT(vcp->iod_thr == curthread);
+
+        switch (vcp->vc_state) {
+        case SMBIOD_ST_NEGOTIATED:
+        case SMBIOD_ST_AUTHCONT:
+                break;
+        default:
+                return (EINVAL);
+        }
+
+        smb_credinit(&scred, cr);
+        if (vcp->vc_flags & SMBV_SMB2)
+                err = smb2_smb_ssnsetup(vcp, &scred);
+        else
+                err = smb_smb_ssnsetup(vcp, &scred);
+        smb_credrele(&scred);
+
+        SMB_VC_LOCK(vcp);
+        switch (err) {
+        case 0:
+                smb_iod_newstate(vcp, SMBIOD_ST_AUTHOK);
+                break;
+        case EINPROGRESS:       /* MORE_PROCESSING_REQUIRED */
+                smb_iod_newstate(vcp, SMBIOD_ST_AUTHCONT);
+                break;
+        default:
+                smb_iod_newstate(vcp, SMBIOD_ST_AUTHFAIL);
+                break;
+        }
+        SMB_VC_UNLOCK(vcp);
+
+        return (err);
+}
+
+static int
+smb_iod_logoff(struct smb_vc *vcp, cred_t *cr)
+{
+        smb_cred_t scred;
+        int err;
+
+        ASSERT(vcp->iod_thr == curthread);
+
+        smb_credinit(&scred, cr);
+        if (vcp->vc_flags & SMBV_SMB2)
+                err = smb2_smb_logoff(vcp, &scred);
+        else
+                err = smb_smb_logoff(vcp, &scred);
+        smb_credrele(&scred);
+
+        return (err);
+}
+
+/*
+ * Handle ioctl SMBIOC_IOD_WORK
+ *
+ * The smbiod agent calls this after authentication to become
+ * the reader for this session, so long as that's possible.
+ * This should only return non-zero if we want that agent to
+ * give up on this VC permanently.
+ */
+/* ARGSUSED */
+int
+smb_iod_vc_work(struct smb_vc *vcp, int flags, cred_t *cr)
+{
+        smbioc_ssn_work_t *wk = &vcp->vc_work;
         int err = 0;
 
         /*
          * This is called by the one-and-only
          * IOD thread for this VC.
          */
         ASSERT(vcp->iod_thr == curthread);
 
         /*
-         * Get the network transport file pointer,
-         * and "loan" it to our transport module.
+         * Should be in state...
          */
-        if ((fp = getf(vcp->vc_tran_fd)) == NULL) {
-                err = EBADF;
-                goto out;
+        if (vcp->vc_state != SMBIOD_ST_AUTHOK) {
+                cmn_err(CE_NOTE, "iod_vc_work: bad state %d", vcp->vc_state);
+                return (EINVAL);
         }
-        if ((err = SMB_TRAN_LOAN_FP(vcp, fp, cr)) != 0)
-                goto out;
 
         /*
-         * In case of reconnect, tell any enqueued requests
-         * then can GO!
+         * Update the session key and initialize SMB signing.
+         *
+         * This implementation does not use multiple SMB sessions per
+         * TCP connection (where only the first session key is used)
+         * so we always have a new session key here.  Sanity check the
+         * length from user space.  Normally 16 or 32.
          */
+        if (wk->wk_u_ssnkey_len > 1024) {
+                cmn_err(CE_NOTE, "iod_vc_work: ssn key too long");
+                return (EINVAL);
+        }
+
+        ASSERT(vcp->vc_ssnkey == NULL);
         SMB_VC_LOCK(vcp);
+        if (wk->wk_u_ssnkey_len != 0 &&
+            wk->wk_u_ssnkey_buf.lp_ptr != NULL) {
+                vcp->vc_ssnkeylen = wk->wk_u_ssnkey_len;
+                vcp->vc_ssnkey = kmem_alloc(vcp->vc_ssnkeylen, KM_SLEEP);
+                if (ddi_copyin(wk->wk_u_ssnkey_buf.lp_ptr,
+                    vcp->vc_ssnkey, vcp->vc_ssnkeylen, flags) != 0) {
+                        err = EFAULT;
+                }
+        }
+        SMB_VC_UNLOCK(vcp);
+        if (err)
+                return (err);
+
+        /*
+         * If we have a session key, derive the MAC key for SMB signing.
+         * If this was a NULL session, we might have no session key.
+         */
+        ASSERT(vcp->vc_mackey == NULL);
+        if (vcp->vc_ssnkey != NULL) {
+                if (vcp->vc_flags & SMBV_SMB2)
+                        err = smb2_sign_init(vcp);
+                else
+                        err = smb_sign_init(vcp);
+                if (err != 0)
+                        return (err);
+        }
+
+        /*
+         * Tell any enqueued requests they can start.
+         */
+        SMB_VC_LOCK(vcp);
         vcp->vc_genid++;        /* possibly new connection */
         smb_iod_newstate(vcp, SMBIOD_ST_VCACTIVE);
         cv_broadcast(&vcp->vc_statechg);
         SMB_VC_UNLOCK(vcp);
 

@@ -1008,13 +1734,15 @@
          */
         if (fscb && fscb->fscb_connect)
                 smb_vc_walkshares(vcp, fscb->fscb_connect);
 
         /*
-         * Run the "reader" loop.
+         * Run the "reader" loop.  An error return here is normal
+         * (i.e. when we need to reconnect) so ignore errors.
+         * Note: This call updates the vc_state.
          */
-        err = smb_iod_recvall(vcp);
+        (void) smb_iod_recvall(vcp, B_FALSE);
 
         /*
          * The reader loop returned, so we must have a
          * new state.  (disconnected or reconnecting)
          *

@@ -1031,61 +1759,79 @@
          *
          * Tell any requests to give up or restart.
          */
         smb_iod_invrq(vcp);
 
-out:
-        /* Recall the file descriptor loan. */
-        (void) SMB_TRAN_LOAN_FP(vcp, NULL, cr);
-        if (fp != NULL) {
-                releasef(vcp->vc_tran_fd);
-        }
-
         return (err);
 }
 
 /*
- * Wait around for someone to ask to use this VC.
- * If the VC has only the IOD reference, then
- * wait only a minute or so, then drop it.
+ * Handle ioctl SMBIOC_IOD_IDLE
+ *
+ * Wait around for someone to ask to use this VC again after the
+ * TCP session has closed.  When one of the connected trees adds a
+ * request, smb_iod_reconnect will set vc_state to RECONNECT and
+ * wake this cv_wait.  When a VC ref. goes away in smb_vc_rele,
+ * that also signals this wait so we can re-check whether we
+ * now hold the last ref. on this VC (and can destroy it).
  */
 int
 smb_iod_vc_idle(struct smb_vc *vcp)
 {
-        clock_t tr, delta = SEC_TO_TICK(15);
         int err = 0;
+        boolean_t destroy = B_FALSE;
 
         /*
          * This is called by the one-and-only
          * IOD thread for this VC.
          */
         ASSERT(vcp->iod_thr == curthread);
 
+        /*
+         * Should be in state...
+         */
+        if (vcp->vc_state != SMBIOD_ST_IDLE &&
+            vcp->vc_state != SMBIOD_ST_RECONNECT) {
+                cmn_err(CE_NOTE, "iod_vc_idle: bad state %d", vcp->vc_state);
+                return (EINVAL);
+        }
+
         SMB_VC_LOCK(vcp);
-        while (vcp->vc_state == SMBIOD_ST_IDLE) {
-                tr = cv_reltimedwait_sig(&vcp->iod_idle, &vcp->vc_lock,
-                    delta, TR_CLOCK_TICK);
-                if (tr == 0) {
+
+        while (vcp->vc_state == SMBIOD_ST_IDLE &&
+            vcp->vc_co.co_usecount > 1) {
+                if (cv_wait_sig(&vcp->iod_idle, &vcp->vc_lock) == 0) {
                         err = EINTR;
                         break;
                 }
-                if (tr < 0) {
-                        /* timeout */
-                        if (vcp->vc_co.co_usecount == 1) {
-                                /* Let this IOD terminate. */
-                                smb_iod_newstate(vcp, SMBIOD_ST_DEAD);
-                                /* nobody to cv_broadcast */
-                                break;
                         }
+        if (vcp->vc_state == SMBIOD_ST_IDLE &&
+            vcp->vc_co.co_usecount == 1) {
+                /*
+                 * We were woken because we now have the last ref.
+                 * Arrange for this VC to be destroyed now.
+                 * Set the "GONE" flag while holding the lock,
+                 * to prevent a race with new references.
+                 * The destroy happens after unlock.
+                 */
+                vcp->vc_flags |= SMBV_GONE;
+                destroy = B_TRUE;
                 }
-        }
+
         SMB_VC_UNLOCK(vcp);
 
+        if (destroy) {
+                /* This sets vc_state = DEAD */
+                smb_iod_disconnect(vcp);
+        }
+
         return (err);
 }
 
 /*
+ * Handle ioctl SMBIOC_IOD_RCFAIL
+ *
  * After a failed reconnect attempt, smbiod will
  * call this to make current requests error out.
  */
 int
 smb_iod_vc_rcfail(struct smb_vc *vcp)

@@ -1096,14 +1842,10 @@
         /*
          * This is called by the one-and-only
          * IOD thread for this VC.
          */
         ASSERT(vcp->iod_thr == curthread);
-
-        if (vcp->vc_state != SMBIOD_ST_RECONNECT)
-                return (EINVAL);
-
         SMB_VC_LOCK(vcp);
 
         smb_iod_newstate(vcp, SMBIOD_ST_RCFAILED);
         cv_broadcast(&vcp->vc_statechg);
 

@@ -1115,12 +1857,22 @@
         tr = cv_reltimedwait_sig(&vcp->iod_idle, &vcp->vc_lock,
             SEC_TO_TICK(5), TR_CLOCK_TICK);
         if (tr == 0)
                 err = EINTR;
 
+        /*
+         * Normally we'll switch to state IDLE here.  However,
+         * if something called smb_iod_reconnect() while we were
+         * waiting above, we'll be in in state reconnect already.
+         * In that case, keep state RECONNECT, so we essentially
+         * skip transition through state IDLE that would normally
+         * happen next.
+         */
+        if (vcp->vc_state != SMBIOD_ST_RECONNECT) {
         smb_iod_newstate(vcp, SMBIOD_ST_IDLE);
         cv_broadcast(&vcp->vc_statechg);
+        }
 
         SMB_VC_UNLOCK(vcp);
 
         return (err);
 }

@@ -1137,15 +1889,21 @@
         SMB_VC_LOCK(vcp);
 again:
         switch (vcp->vc_state) {
 
         case SMBIOD_ST_IDLE:
+                /* Tell the IOD thread it's no longer IDLE. */
                 smb_iod_newstate(vcp, SMBIOD_ST_RECONNECT);
                 cv_signal(&vcp->iod_idle);
                 /* FALLTHROUGH */
 
         case SMBIOD_ST_RECONNECT:
+        case SMBIOD_ST_CONNECTED:
+        case SMBIOD_ST_NEGOTIATED:
+        case SMBIOD_ST_AUTHCONT:
+        case SMBIOD_ST_AUTHOK:
+                /* Wait for the VC state to become ACTIVE. */
                 rv = cv_wait_sig(&vcp->vc_statechg, &vcp->vc_lock);
                 if (rv == 0) {
                         err = EINTR;
                         break;
                 }

@@ -1153,10 +1911,11 @@
 
         case SMBIOD_ST_VCACTIVE:
                 err = 0; /* success! */
                 break;
 
+        case SMBIOD_ST_AUTHFAIL:
         case SMBIOD_ST_RCFAILED:
         case SMBIOD_ST_DEAD:
         default:
                 err = ENOTCONN;
                 break;