Print this page
NEX-20459 smb/server service restart fails, stuck in offlining state
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-4856 SMB2 kstats don't correctly count compound requests
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-18748 (Hyper-V 2016) VM goes to poweroff state when smbd is restarted
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Joyce McIntosh <joyce.mcintosh@nexenta.com>
NEX-17589 Get "too high" smbd error when copy big file to cifs share (redo)
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-16519 Panic while running IOmeter to a pool through an SMB share
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15578 SMB2 durable handle redesign
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15581 SMB keep-alive feature is just noise
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-5665 SMB2 oplock leases
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-9808 SMB3 persistent handles
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15578 SMB2 durable handle redesign
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-15581 SMB keep-alive feature is just noise
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-5665 SMB2 oplock leases
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-1643 dtrace provider for smbsrv
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-9864 Some SMB cancel races remain after NEX-5845
Revert (part of) "NEX-5845 rework SMB immediate cancel"
reverts (part of) commit 7a5da69f6d42b17ebcc95ca3d02925d07a01343e.
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-5273 SMB 3 Encryption
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
Reviewed by: Roman Strashkin <roman.strashkin@nexenta.com>
NEX-8120 smbstat -C requires IPv4 addresses to be prefixed with ::ffff: if ipv6_enable is true
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Jean McCormack <jean.mccormack@nexenta.com>
NEX-6308 namespace collision for per-share kstats when changing sharesmb property
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
Reviewed by: Evan Layton <evan.layton@nexenta.com>
NEX-6041 Should pass the smbtorture lock tests
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-5845 rework SMB immediate cancel
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-3553 SMB2/3 durable handles
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-5560 smb2 should use 64-bit server-global uids
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5537 Want reference counts for users, trees...
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5330 SMB server should combine TCP+NBT session lists
Reviewed by: Gordon Ross <gwr@nexenta.com>
NEX-5314 SMB server may wait for oplock break ack on disconnected session
Reviewed by: Matt Barden <matt.barden@nexenta.com>
NEX-3906 Prefer that SMB change notify not tie up a worker thread
NEX-5278 SMB notify should buffer per file handle
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-5175 want SMB statistics separately for reads, writes, other
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4313 want iops, bandwidth, and latency kstats for smb
Portions contributed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
Reviewed by: Gordon Ross <gordon.ross@nexenta.com>
NEX-4714 smbstat -t tbytes/s always zero
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-2522 svcadm disable network/smb/server may hang
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4391 improve smb cancel support
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-4147 Cancelled SMB2 requests sometimes have no response (missed things)
NEX-4083 Upstream changes from illumos 5917 and 5995
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
SUP-672 Zero-padded IP address strings returned by SMB server...
Reviewed by: Yuri Pankov <yuri.pankov@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-3738 Should support SMB2_CAP_LARGE_MTU
Reviewed by: Alek Pinchuk <alek@nexenta.com>
Reviewed by: Bayard Bell <bayard.bell@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
NEX-3610 CLONE NEX-3591 SMB3 signing
Reviewed by: Gordon Ross <gwr@nexenta.com>
Reviewed by: Dan Fields <dan.fields@nexenta.com>
NEX-3004 Keep track of remote TCP port
Reviewed by: Dan Fields <Dan.Fields@nexenta.com>
Reviewed by: Josef Sipek <Josef.Sipek@nexenta.com>
Reviewed by: Matt Barden <Matt.Barden@nexenta.com>
Reviewed by: Kevin Crowe <Kevin.Crowe@nexenta.com>
Reviewed by: Alek Pinchuk <Alek.Pinchuk@nexenta.com>
Reviewed by: Rick McNeal <Rick.McNeal@nexenta.com>
NEX-2798 SMB 1 disconnects after large write attempt
NEX-2781 SMB2 credit handling needs work
NEX-2353 Codenomicon: SMB2 TC # 448950 - PANIC in SMB2.Compounded-commands...
NEX-1991 SMB2 never grants level II oplocks
SMB-137 assertion failed: (sr->reply.chain_offset & 7) == 0, file: ../../common/fs/smbsrv/smb2_dispatch.c, line: 727
SMB-11 SMB2 message parse & dispatch
SMB-12 SMB2 Negotiate Protocol
SMB-13 SMB2 Session Setup
SMB-14 SMB2 Logoff
SMB-15 SMB2 Tree Connect
SMB-16 SMB2 Tree Disconnect
SMB-17 SMB2 Create
SMB-18 SMB2 Close
SMB-19 SMB2 Flush
SMB-20 SMB2 Read
SMB-21 SMB2 Write
SMB-22 SMB2 Lock/Unlock
SMB-23 SMB2 Ioctl
SMB-24 SMB2 Cancel
SMB-25 SMB2 Echo
SMB-26 SMB2 Query Dir
SMB-27 SMB2 Change Notify
SMB-28 SMB2 Query Info
SMB-29 SMB2 Set Info
SMB-30 SMB2 Oplocks
SMB-53 SMB2 Create Context options
(SMB2 code review cleanup 1, 2, 3)
SMB-50 User-mode SMB server (fix elfchk noise)
SMB-56 extended security NTLMSSP, inbound (fix a leak)
SMB-39 Use AF_UNIX pipes for RPC (fix a leak)
SMB-75 smb_session_timers way too frequent
SMB-69 read-raw, write-raw are dead code
SMB-56 extended security NTLMSSP, inbound
SMB-50 User-mode SMB server
 Includes work by these authors:
 Thomas Keiser <thomas.keiser@nexenta.com>
 Albert Lee <trisk@nexenta.com>
SMB-65 SMB server in non-global zones (kmem_caches)
common kmem_cache instances across zones
separate GZ-only init from NGZ init
SMB-63 taskq_create_proc ... TQ_DYNAMIC puts tasks in p0
re #11974 CIFS Share - Tree connect fails from Windows 7 Clients
Fix up some merges where we wanted the upstream version.
SMB-48 Panic with smbtorture raw.scan-eamax
re #7126 rb4153 smbd panic with missing negotiate challenge
re #11215 rb3676 sesctl to SGI JBOD hangs in biowait() with a command stuck in mptsas driver
re #10734 NT Trans. Notify returning too quickly
re #6813 rb1757 port 2976 Child folder visibility through shares

@@ -18,11 +18,11 @@
  *
  * CDDL HEADER END
  */
 /*
  * Copyright (c) 2007, 2010, Oracle and/or its affiliates. All rights reserved.
- * Copyright 2015 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2019 Nexenta Systems, Inc.  All rights reserved.
  */
 
 #include <sys/atomic.h>
 #include <sys/synch.h>
 #include <sys/types.h>

@@ -44,27 +44,62 @@
  * We track the keepalive in minutes, but this constant
  * specifies it in seconds, so convert to minutes.
  */
 uint32_t smb_keep_alive = SMB_PI_KEEP_ALIVE_MIN / 60;
 
+/*
+ * There are many smbtorture test cases that send
+ * racing requests, and where the tests fail if we
+ * don't execute them in exactly the order sent.
+ * These are test bugs.  The protocol makes no
+ * guarantees about execution order of requests
+ * that are concurrently active.
+ *
+ * Nonetheless, smbtorture has many useful tests,
+ * so we have this work-around we can enable to
+ * basically force sequential execution.  When
+ * enabled, insert a delay after each request is
+ * issued a taskq job.  Enable this with mdb by
+ * setting smb_reader_delay to 10.  Don't make it
+ * more than 500 or so or the server will appear
+ * to be so slow that tests may time out.
+ */
+int smb_reader_delay = 0;  /* mSec. */
+
 static int  smbsr_newrq_initial(smb_request_t *);
 
 static void smb_session_cancel(smb_session_t *);
 static int smb_session_reader(smb_session_t *);
 static int smb_session_xprt_puthdr(smb_session_t *,
     uint8_t msg_type, uint32_t msg_len,
     uint8_t *dst, size_t dstlen);
-static smb_tree_t *smb_session_get_tree(smb_session_t *, smb_tree_t *);
-static void smb_session_logoff(smb_session_t *);
+static void smb_session_disconnect_trees(smb_session_t  *);
 static void smb_request_init_command_mbuf(smb_request_t *sr);
 static void smb_session_genkey(smb_session_t *);
+static int smb_session_kstat_update(kstat_t *, int);
+void session_stats_init(smb_server_t *, smb_session_t *);
+void session_stats_fini(smb_session_t *);
 
+/*
+ * This (legacy) code is in support of an "idle timeout" feature,
+ * which is apparently incomplete.  To complete it, we should:
+ * when the keep_alive timer expires, check whether the client
+ * has any open files, and if not then kill their session.
+ * Right now the timers are there, but nothing happens when
+ * a timer expires.
+ *
+ * Todo: complete logic to kill idle sessions.
+ *
+ * Only called when sv_cfg.skc_keepalive != 0
+ */
 void
-smb_session_timers(smb_llist_t *ll)
+smb_session_timers(smb_server_t *sv)
 {
         smb_session_t   *session;
+        smb_llist_t     *ll;
 
+        ll = &sv->sv_session_list;
         smb_llist_enter(ll, RW_READER);
         session = smb_llist_head(ll);
         while (session != NULL) {
                 /*
                  * Walk through the table and decrement each keep_alive

@@ -72,48 +107,16 @@
                  */
                 SMB_SESSION_VALID(session);
                 if (session->keep_alive &&
                     (session->keep_alive != (uint32_t)-1))
                         session->keep_alive--;
+
                 session = smb_llist_next(ll, session);
         }
         smb_llist_exit(ll);
 }
 
-void
-smb_session_correct_keep_alive_values(smb_llist_t *ll, uint32_t new_keep_alive)
-{
-        smb_session_t           *sn;
-
-        /*
-         * Caller specifies seconds, but we track in minutes, so
-         * convert to minutes (rounded up).
-         */
-        new_keep_alive = (new_keep_alive + 59) / 60;
-
-        if (new_keep_alive == smb_keep_alive)
-                return;
-        /*
-         * keep alive == 0 means do not drop connection if it's idle
-         */
-        smb_keep_alive = (new_keep_alive) ? new_keep_alive : -1;
-
-        /*
-         * Walk through the table and set each session to the new keep_alive
-         * value if they have not already timed out.  Block clock interrupts.
-         */
-        smb_llist_enter(ll, RW_READER);
-        sn = smb_llist_head(ll);
-        while (sn != NULL) {
-                SMB_SESSION_VALID(sn);
-                if (sn->keep_alive != 0)
-                        sn->keep_alive = new_keep_alive;
-                sn = smb_llist_next(ll, sn);
-        }
-        smb_llist_exit(ll);
-}
-
 /*
  * Send a session message - supports SMB-over-NBT and SMB-over-TCP.
  * If an mbuf chain is provided (optional), it will be freed and
  * set to NULL -- unconditionally!  (error or not)
  *

@@ -415,47 +418,44 @@
  * state.
  */
 void
 smb_request_cancel(smb_request_t *sr)
 {
+        void (*cancel_method)(smb_request_t *) = NULL;
+
         mutex_enter(&sr->sr_mutex);
         switch (sr->sr_state) {
 
         case SMB_REQ_STATE_INITIALIZING:
         case SMB_REQ_STATE_SUBMITTED:
         case SMB_REQ_STATE_ACTIVE:
         case SMB_REQ_STATE_CLEANED_UP:
-                sr->sr_state = SMB_REQ_STATE_CANCELED;
+                sr->sr_state = SMB_REQ_STATE_CANCELLED;
                 break;
 
+        case SMB_REQ_STATE_WAITING_AUTH:
+        case SMB_REQ_STATE_WAITING_FCN1:
         case SMB_REQ_STATE_WAITING_LOCK:
+        case SMB_REQ_STATE_WAITING_PIPE:
                 /*
-                 * This request is waiting on a lock.  Wakeup everything
-                 * waiting on the lock so that the relevant thread regains
-                 * control and notices that is has been canceled.  The
-                 * other lock request threads waiting on this lock will go
-                 * back to sleep when they discover they are still blocked.
+                 * These are states that have a cancel_method.
+                 * Make the state change now, to ensure that
+                 * we call cancel_method exactly once.  Do the
+                 * method call below, after we drop sr_mutex.
+                 * When the cancelled request thread resumes,
+                 * it should re-take sr_mutex and set sr_state
+                 * to CANCELLED, then return STATUS_CANCELLED.
                  */
-                sr->sr_state = SMB_REQ_STATE_CANCELED;
-
-                ASSERT(sr->sr_awaiting != NULL);
-                mutex_enter(&sr->sr_awaiting->l_mutex);
-                cv_broadcast(&sr->sr_awaiting->l_cv);
-                mutex_exit(&sr->sr_awaiting->l_mutex);
+                sr->sr_state = SMB_REQ_STATE_CANCEL_PENDING;
+                cancel_method = sr->cancel_method;
+                VERIFY(cancel_method != NULL);
                 break;
 
-        case SMB_REQ_STATE_WAITING_EVENT:
-                /*
-                 * This request is waiting in change notify.
-                 */
-                sr->sr_state = SMB_REQ_STATE_CANCELED;
-                cv_signal(&sr->sr_ncr.nc_cv);
-                break;
-
-        case SMB_REQ_STATE_EVENT_OCCURRED:
+        case SMB_REQ_STATE_WAITING_FCN2:
         case SMB_REQ_STATE_COMPLETED:
-        case SMB_REQ_STATE_CANCELED:
+        case SMB_REQ_STATE_CANCEL_PENDING:
+        case SMB_REQ_STATE_CANCELLED:
                 /*
                  * No action required for these states since the request
                  * is completing.
                  */
                 break;

@@ -463,16 +463,25 @@
         case SMB_REQ_STATE_FREE:
         default:
                 SMB_PANIC();
         }
         mutex_exit(&sr->sr_mutex);
+
+        if (cancel_method != NULL) {
+                cancel_method(sr);
+        }
 }
 
 /*
  * smb_session_receiver
  *
  * Receives request from the network and dispatches them to a worker.
+ *
+ * When we receive a disconnect here, it _could_ be due to the server
+ * having initiated disconnect, in which case the session state will be
+ * SMB_SESSION_STATE_TERMINATED and we want to keep that state so later
+ * tear-down logic will know which side initiated.
  */
 void
 smb_session_receiver(smb_session_t *session)
 {
         int     rc = 0;

@@ -483,11 +492,13 @@
 
         if (session->s_local_port == IPPORT_NETBIOS_SSN) {
                 rc = smb_netbios_session_request(session);
                 if (rc != 0) {
                         smb_rwx_rwenter(&session->s_lock, RW_WRITER);
-                        session->s_state = SMB_SESSION_STATE_DISCONNECTED;
+                        if (session->s_state != SMB_SESSION_STATE_TERMINATED)
+                                session->s_state =
+                                    SMB_SESSION_STATE_DISCONNECTED;
                         smb_rwx_rwexit(&session->s_lock);
                         return;
                 }
         }
 

@@ -496,10 +507,11 @@
         smb_rwx_rwexit(&session->s_lock);
 
         (void) smb_session_reader(session);
 
         smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+        if (session->s_state != SMB_SESSION_STATE_TERMINATED)
         session->s_state = SMB_SESSION_STATE_DISCONNECTED;
         smb_rwx_rwexit(&session->s_lock);
 
         smb_soshutdown(session->sock);
 

@@ -514,11 +526,11 @@
 }
 
 /*
  * smb_session_disconnect
  *
- * Disconnects the session passed in.
+ * Server-initiated disconnect (i.e. server shutdown)
  */
 void
 smb_session_disconnect(smb_session_t *session)
 {
         SMB_SESSION_VALID(session);

@@ -528,12 +540,12 @@
         case SMB_SESSION_STATE_INITIALIZED:
         case SMB_SESSION_STATE_CONNECTED:
         case SMB_SESSION_STATE_ESTABLISHED:
         case SMB_SESSION_STATE_NEGOTIATED:
                 smb_soshutdown(session->sock);
-                session->s_state = SMB_SESSION_STATE_DISCONNECTED;
-                _NOTE(FALLTHRU)
+                session->s_state = SMB_SESSION_STATE_TERMINATED;
+                break;
         case SMB_SESSION_STATE_DISCONNECTED:
         case SMB_SESSION_STATE_TERMINATED:
                 break;
         }
         smb_rwx_rwexit(&session->s_lock);

@@ -600,12 +612,15 @@
 
                 session->keep_alive = smb_keep_alive;
 
                 /*
                  * Allocate a request context, read the whole message.
+                 * If the request alloc fails, we've disconnected
+                 * and won't be able to send the reply anyway, so bail now.
                  */
-                sr = smb_request_alloc(session, hdr.xh_length);
+                if ((sr = smb_request_alloc(session, hdr.xh_length)) == NULL)
+                        break;
 
                 req_buf = (uint8_t *)sr->sr_request_buf;
                 resid = hdr.xh_length;
 
                 rc = smb_sorecv(session->sock, req_buf, resid);

@@ -612,12 +627,11 @@
                 if (rc) {
                         smb_request_free(sr);
                         break;
                 }
 
-                /* accounting: requests, received bytes */
-                smb_server_inc_req(sv);
+                /* accounting: received bytes */
                 smb_server_add_rxb(sv,
                     (int64_t)(hdr.xh_length + NETBIOS_HDR_SZ));
 
                 /*
                  * Initialize command MBC to represent the received data.

@@ -628,11 +642,16 @@
 
                 rc = session->newrq_func(sr);
                 sr = NULL;      /* enqueued or freed */
                 if (rc != 0)
                         break;
+
+                /* See notes where this is defined (above). */
+                if (smb_reader_delay) {
+                        delay(MSEC_TO_TICK(smb_reader_delay));
         }
+        }
         return (rc);
 }
 
 /*
  * This is the initial handler for new smb requests, called from

@@ -647,10 +666,13 @@
  *
  * This and other "post a request" handlers must either enqueue
  * the new request for the session taskq, or smb_request_free it
  * (in case we've decided to drop this connection).  In this
  * (special) new request handler, we always free the request.
+ *
+ * Return value is 0 for success, and anything else will
+ * terminate the reader thread (drop the connection).
  */
 static int
 smbsr_newrq_initial(smb_request_t *sr)
 {
         uint32_t magic;

@@ -701,17 +723,17 @@
                 return (NULL);
         }
 
         now = ddi_get_lbolt64();
 
+        session->s_server = sv;
         session->s_kid = SMB_NEW_KID();
         session->s_state = SMB_SESSION_STATE_INITIALIZED;
         session->native_os = NATIVE_OS_UNKNOWN;
         session->opentime = now;
         session->keep_alive = smb_keep_alive;
         session->activity_timestamp = now;
-
         smb_session_genkey(session);
 
         mutex_init(&session->s_credits_mutex, NULL, MUTEX_DEFAULT, NULL);
 
         smb_slist_constructor(&session->s_req_list, sizeof (smb_request_t),

@@ -728,11 +750,26 @@
 
         smb_net_txl_constructor(&session->s_txlst);
 
         smb_rwx_init(&session->s_lock);
 
-        if (new_so != NULL) {
+        session->s_srqueue = &sv->sv_srqueue;
+        smb_server_get_cfg(sv, &session->s_cfg);
+
+        if (new_so == NULL) {
+                /*
+                 * This call is creating the special "server" session,
+                 * used for kshare export, oplock breaks, CA import.
+                 * CA import creates temporary trees on this session
+                 * and those should never get map/unmap up-calls, so
+                 * force the map/unmap flags zero on this session.
+                 * Set a "modern" dialect for CA import too, so
+                 * pathname parse doesn't do OS/2 stuff, etc.
+                 */
+                session->s_cfg.skc_execflags = 0;
+                session->dialect = session->s_cfg.skc_max_protocol;
+        } else {
                 if (family == AF_INET) {
                         slen = sizeof (sin);
                         (void) ksocket_getsockname(new_so,
                             (struct sockaddr *)&sin, &slen, CRED());
                         bcopy(&sin.sin_addr,

@@ -770,13 +807,10 @@
                 if (port == IPPORT_NETBIOS_SSN)
                         smb_server_inc_nbt_sess(sv);
                 else
                         smb_server_inc_tcp_sess(sv);
         }
-        session->s_server = sv;
-        smb_server_get_cfg(sv, &session->s_cfg);
-        session->s_srqueue = &sv->sv_srqueue;
 
         /*
          * The initial new request handler is special,
          * and only accepts negotiation requests.
          */

@@ -784,20 +818,133 @@
 
         /* These may increase in SMB2 negotiate. */
         session->cmd_max_bytes = SMB_REQ_MAX_SIZE;
         session->reply_max_bytes = SMB_REQ_MAX_SIZE;
 
+        session_stats_init(sv, session);
+
         session->s_magic = SMB_SESSION_MAGIC;
+
         return (session);
 }
 
 void
+session_stats_init(smb_server_t *sv, smb_session_t *ss)
+{
+        static const char       *kr_names[] = SMBSRV_CLSH__NAMES;
+        char                    ks_name[KSTAT_STRLEN];
+        char                    *ipaddr_str = ss->ip_addr_str;
+        smbsrv_clsh_kstats_t    *ksr;
+        int                     idx;
+
+        /* Don't include the special internal session (sv->sv_session). */
+        if (ss->sock == NULL)
+                return;
+
+        /*
+         * If ipv6_enable is set to true, IPv4 addresses will be prefixed
+         * with ::ffff: (IPv4-mapped IPv6 address), strip it.
+         */
+        if (strncasecmp(ipaddr_str, "::ffff:", 7) == 0)
+                ipaddr_str += 7;
+
+        /*
+         * Create raw kstats for sessions with a name composed as:
+         * cl/$IPADDR  and instance ss->s_kid
+         * These will look like: smbsrv:0:cl/10.10.0.5
+         */
+        (void) snprintf(ks_name, sizeof (ks_name), "cl/%s", ipaddr_str);
+
+        ss->s_ksp = kstat_create_zone(SMBSRV_KSTAT_MODULE, ss->s_kid,
+            ks_name, SMBSRV_KSTAT_CLASS, KSTAT_TYPE_RAW,
+            sizeof (smbsrv_clsh_kstats_t), 0, sv->sv_zid);
+
+        if (ss->s_ksp == NULL)
+                return;
+
+        ss->s_ksp->ks_update = smb_session_kstat_update;
+        ss->s_ksp->ks_private = ss;
+
+        /*
+         * In-line equivalent of smb_dispatch_stats_init
+         */
+        ksr = (smbsrv_clsh_kstats_t *)ss->s_ksp->ks_data;
+        for (idx = 0; idx < SMBSRV_CLSH__NREQ; idx++) {
+                smb_latency_init(&ss->s_stats[idx].sdt_lat);
+                (void) strlcpy(ksr->ks_clsh[idx].kr_name, kr_names[idx],
+                    KSTAT_STRLEN);
+        }
+
+        kstat_install(ss->s_ksp);
+}
+
+void
+session_stats_fini(smb_session_t *ss)
+{
+        int     idx;
+
+        for (idx = 0; idx < SMBSRV_CLSH__NREQ; idx++)
+                smb_latency_destroy(&ss->s_stats[idx].sdt_lat);
+}
+
+/*
+ * Update the kstat data from our private stats.
+ */
+static int
+smb_session_kstat_update(kstat_t *ksp, int rw)
+{
+        smb_session_t *session;
+        smb_disp_stats_t *sds;
+        smbsrv_clsh_kstats_t    *clsh;
+        smb_kstat_req_t *ksr;
+        int i;
+
+        if (rw == KSTAT_WRITE)
+                return (EACCES);
+
+        session = ksp->ks_private;
+        SMB_SESSION_VALID(session);
+        sds = session->s_stats;
+
+        clsh = (smbsrv_clsh_kstats_t *)ksp->ks_data;
+        ksr = clsh->ks_clsh;
+
+        for (i = 0; i < SMBSRV_CLSH__NREQ; i++, ksr++, sds++) {
+                ksr->kr_rxb = sds->sdt_rxb;
+                ksr->kr_txb = sds->sdt_txb;
+                mutex_enter(&sds->sdt_lat.ly_mutex);
+                ksr->kr_nreq = sds->sdt_lat.ly_a_nreq;
+                ksr->kr_sum = sds->sdt_lat.ly_a_sum;
+                ksr->kr_a_mean = sds->sdt_lat.ly_a_mean;
+                ksr->kr_a_stddev = sds->sdt_lat.ly_a_stddev;
+                ksr->kr_d_mean = sds->sdt_lat.ly_d_mean;
+                ksr->kr_d_stddev = sds->sdt_lat.ly_d_stddev;
+                sds->sdt_lat.ly_d_mean = 0;
+                sds->sdt_lat.ly_d_nreq = 0;
+                sds->sdt_lat.ly_d_stddev = 0;
+                sds->sdt_lat.ly_d_sum = 0;
+                mutex_exit(&sds->sdt_lat.ly_mutex);
+        }
+
+        return (0);
+}
+
+void
 smb_session_delete(smb_session_t *session)
 {
 
         ASSERT(session->s_magic == SMB_SESSION_MAGIC);
 
+        if (session->s_ksp != NULL) {
+                kstat_delete(session->s_ksp);
+                session->s_ksp = NULL;
+                session_stats_fini(session);
+        }
+
+        if (session->enc_mech != NULL)
+                smb3_encrypt_fini(session);
+
         if (session->sign_fini != NULL)
                 session->sign_fini(session);
 
         if (session->signing.mackey != NULL) {
                 kmem_free(session->signing.mackey,

@@ -845,21 +992,24 @@
          * this session.
          */
         smb_slist_wait_for_empty(&session->s_req_list);
 
         /*
-         * At this point the reference count of the users, trees, files,
-         * directories should be zero. It should be possible to destroy them
-         * without any problem.
+         * Cleanup transact state objects
          */
         xa = smb_llist_head(&session->s_xa_list);
         while (xa) {
                 nextxa = smb_llist_next(&session->s_xa_list, xa);
                 smb_xa_close(xa);
                 xa = nextxa;
         }
 
+        /*
+         * At this point the reference count of the files and directories
+         * should be zero. It should be possible to destroy them without
+         * any problem, which should trigger the destruction of other objects.
+         */
         smb_session_logoff(session);
 }
 
 /*
  * Cancel requests.  If a non-null tree is specified, only requests specific

@@ -893,62 +1043,67 @@
  * Find a user on the specified session by SMB UID.
  */
 smb_user_t *
 smb_session_lookup_uid(smb_session_t *session, uint16_t uid)
 {
-        return (smb_session_lookup_uid_st(session, uid,
+        return (smb_session_lookup_uid_st(session, 0, uid,
             SMB_USER_STATE_LOGGED_ON));
 }
 
+/*
+ * Find a user on the specified session by SMB2 SSNID.
+ */
 smb_user_t *
-smb_session_lookup_uid_st(smb_session_t *session, uint16_t uid,
-    smb_user_state_t st)
+smb_session_lookup_ssnid(smb_session_t *session, uint64_t ssnid)
 {
+        return (smb_session_lookup_uid_st(session, ssnid, 0,
+            SMB_USER_STATE_LOGGED_ON));
+}
+
+smb_user_t *
+smb_session_lookup_uid_st(smb_session_t *session, uint64_t ssnid,
+    uint16_t uid, smb_user_state_t st)
+{
         smb_user_t      *user;
         smb_llist_t     *user_list;
 
         SMB_SESSION_VALID(session);
 
         user_list = &session->s_user_list;
         smb_llist_enter(user_list, RW_READER);
 
-        user = smb_llist_head(user_list);
-        while (user) {
+        for (user = smb_llist_head(user_list);
+            user != NULL;
+            user = smb_llist_next(user_list, user)) {
+
                 SMB_USER_VALID(user);
                 ASSERT(user->u_session == session);
 
-                if (user->u_uid == uid && user->u_state == st) {
-                        smb_user_hold_internal(user);
+                if (user->u_ssnid != ssnid && user->u_uid != uid)
+                        continue;
+
+                mutex_enter(&user->u_mutex);
+                if (user->u_state == st) {
+                        // smb_user_hold_internal(user);
+                        user->u_refcnt++;
+                        mutex_exit(&user->u_mutex);
                         break;
                 }
-
-                user = smb_llist_next(user_list, user);
+                mutex_exit(&user->u_mutex);
         }
 
         smb_llist_exit(user_list);
         return (user);
 }
 
-void
-smb_session_post_user(smb_session_t *session, smb_user_t *user)
-{
-        SMB_USER_VALID(user);
-        ASSERT(user->u_refcnt == 0);
-        ASSERT(user->u_state == SMB_USER_STATE_LOGGED_OFF);
-        ASSERT(user->u_session == session);
-
-        smb_llist_post(&session->s_user_list, user, smb_user_delete);
-}
-
 /*
  * Find a tree by tree-id.
  */
 smb_tree_t *
 smb_session_lookup_tree(
     smb_session_t       *session,
     uint16_t            tid)
-
 {
         smb_tree_t      *tree;
 
         SMB_SESSION_VALID(session);
 

@@ -975,121 +1130,39 @@
         smb_llist_exit(&session->s_tree_list);
         return (NULL);
 }
 
 /*
- * Find the first connected tree that matches the specified sharename.
- * If the specified tree is NULL the search starts from the beginning of
- * the user's tree list.  If a tree is provided the search starts just
- * after that tree.
- */
-smb_tree_t *
-smb_session_lookup_share(
-    smb_session_t       *session,
-    const char          *sharename,
-    smb_tree_t          *tree)
-{
-        SMB_SESSION_VALID(session);
-        ASSERT(sharename);
-
-        smb_llist_enter(&session->s_tree_list, RW_READER);
-
-        if (tree) {
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                ASSERT(tree->t_session == session);
-                tree = smb_llist_next(&session->s_tree_list, tree);
-        } else {
-                tree = smb_llist_head(&session->s_tree_list);
-        }
-
-        while (tree) {
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                ASSERT(tree->t_session == session);
-                if (smb_strcasecmp(tree->t_sharename, sharename, 0) == 0) {
-                        if (smb_tree_hold(tree)) {
-                                smb_llist_exit(&session->s_tree_list);
-                                return (tree);
-                        }
-                }
-                tree = smb_llist_next(&session->s_tree_list, tree);
-        }
-
-        smb_llist_exit(&session->s_tree_list);
-        return (NULL);
-}
-
-/*
- * Find the first connected tree that matches the specified volume name.
- * If the specified tree is NULL the search starts from the beginning of
- * the user's tree list.  If a tree is provided the search starts just
- * after that tree.
- */
-smb_tree_t *
-smb_session_lookup_volume(
-    smb_session_t       *session,
-    const char          *name,
-    smb_tree_t          *tree)
-{
-        SMB_SESSION_VALID(session);
-        ASSERT(name);
-
-        smb_llist_enter(&session->s_tree_list, RW_READER);
-
-        if (tree) {
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                ASSERT(tree->t_session == session);
-                tree = smb_llist_next(&session->s_tree_list, tree);
-        } else {
-                tree = smb_llist_head(&session->s_tree_list);
-        }
-
-        while (tree) {
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                ASSERT(tree->t_session == session);
-
-                if (smb_strcasecmp(tree->t_volume, name, 0) == 0) {
-                        if (smb_tree_hold(tree)) {
-                                smb_llist_exit(&session->s_tree_list);
-                                return (tree);
-                        }
-                }
-
-                tree = smb_llist_next(&session->s_tree_list, tree);
-        }
-
-        smb_llist_exit(&session->s_tree_list);
-        return (NULL);
-}
-
-/*
  * Disconnect all trees that match the specified client process-id.
+ * Used by the SMB1 "process exit" request.
  */
 void
 smb_session_close_pid(
     smb_session_t       *session,
     uint32_t            pid)
 {
+        smb_llist_t     *tree_list = &session->s_tree_list;
         smb_tree_t      *tree;
 
-        SMB_SESSION_VALID(session);
+        smb_llist_enter(tree_list, RW_READER);
 
-        tree = smb_session_get_tree(session, NULL);
+        tree = smb_llist_head(tree_list);
         while (tree) {
-                smb_tree_t *next;
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                ASSERT(tree->t_session == session);
+                if (smb_tree_hold(tree)) {
                 smb_tree_close_pid(tree, pid);
-                next = smb_session_get_tree(session, tree);
                 smb_tree_release(tree);
-                tree = next;
         }
+                tree = smb_llist_next(tree_list, tree);
+        }
+
+        smb_llist_exit(tree_list);
 }
 
 static void
-smb_session_tree_dtor(void *t)
+smb_session_tree_dtor(void *arg)
 {
-        smb_tree_t      *tree = (smb_tree_t *)t;
+        smb_tree_t      *tree = arg;
 
         smb_tree_disconnect(tree, B_TRUE);
         /* release the ref acquired during the traversal loop */
         smb_tree_release(tree);
 }

@@ -1116,12 +1189,13 @@
                 if ((tree->t_owner == owner) &&
                     smb_tree_hold(tree)) {
                         /*
                          * smb_tree_hold() succeeded, hence we are in state
                          * SMB_TREE_STATE_CONNECTED; schedule this tree
-                         * for asynchronous disconnect, which will fire
-                         * after we drop the llist traversal lock.
+                         * for disconnect after smb_llist_exit because
+                         * the "unmap exec" up-call can block, and we'd
+                         * rather not block with the tree list locked.
                          */
                         smb_llist_post(tree_list, tree, smb_session_tree_dtor);
                 }
                 tree = smb_llist_next(tree_list, tree);
         }

@@ -1131,145 +1205,189 @@
 }
 
 /*
  * Disconnect all trees that this user has connected.
  */
-void
+static void
 smb_session_disconnect_trees(
     smb_session_t       *session)
 {
+        smb_llist_t     *tree_list = &session->s_tree_list;
         smb_tree_t      *tree;
 
-        SMB_SESSION_VALID(session);
+        smb_llist_enter(tree_list, RW_READER);
 
-        tree = smb_session_get_tree(session, NULL);
+        tree = smb_llist_head(tree_list);
         while (tree) {
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                ASSERT(tree->t_session == session);
-                smb_tree_disconnect(tree, B_TRUE);
-                smb_tree_release(tree);
-                tree = smb_session_get_tree(session, NULL);
+                if (smb_tree_hold(tree)) {
+                        smb_llist_post(tree_list, tree,
+                            smb_session_tree_dtor);
         }
+                tree = smb_llist_next(tree_list, tree);
+        }
+
+        /* drop the lock and flush the dtor queue */
+        smb_llist_exit(tree_list);
 }
 
 /*
- * Disconnect all trees that match the specified share name.
+ * Variant of smb_session_tree_dtor that also
+ * cancels requests using this tree.
  */
-void
-smb_session_disconnect_share(
-    smb_session_t       *session,
-    const char          *sharename)
+static void
+smb_session_tree_kill(void *arg)
 {
-        smb_tree_t      *tree;
-        smb_tree_t      *next;
+        smb_tree_t      *tree = arg;
 
-        SMB_SESSION_VALID(session);
+        SMB_TREE_VALID(tree);
 
-        tree = smb_session_lookup_share(session, sharename, NULL);
-        while (tree) {
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                ASSERT(tree->t_session == session);
-                smb_session_cancel_requests(session, tree, NULL);
                 smb_tree_disconnect(tree, B_TRUE);
-                next = smb_session_lookup_share(session, sharename, tree);
+        smb_session_cancel_requests(tree->t_session, tree, NULL);
+
+        /* release the ref acquired during the traversal loop */
                 smb_tree_release(tree);
-                tree = next;
-        }
 }
 
-void
-smb_session_post_tree(smb_session_t *session, smb_tree_t *tree)
-{
-        SMB_SESSION_VALID(session);
-        SMB_TREE_VALID(tree);
-        ASSERT0(tree->t_refcnt);
-        ASSERT(tree->t_state == SMB_TREE_STATE_DISCONNECTED);
-        ASSERT(tree->t_session == session);
-
-        smb_llist_post(&session->s_tree_list, tree, smb_tree_dealloc);
-}
-
 /*
- * Get the next connected tree in the list.  A reference is taken on
- * the tree, which can be released later with smb_tree_release().
- *
- * If the specified tree is NULL the search starts from the beginning of
- * the tree list.  If a tree is provided the search starts just after
- * that tree.
- *
- * Returns NULL if there are no connected trees in the list.
+ * Disconnect all trees that match the specified share name,
+ * and kill requests using those trees.
  */
-static smb_tree_t *
-smb_session_get_tree(
+void
+smb_session_disconnect_share(
     smb_session_t       *session,
-    smb_tree_t          *tree)
+    const char          *sharename)
 {
-        smb_llist_t     *tree_list;
+        smb_llist_t     *ll;
+        smb_tree_t      *tree;
 
         SMB_SESSION_VALID(session);
-        tree_list = &session->s_tree_list;
 
-        smb_llist_enter(tree_list, RW_READER);
+        ll = &session->s_tree_list;
+        smb_llist_enter(ll, RW_READER);
 
-        if (tree) {
-                ASSERT3U(tree->t_magic, ==, SMB_TREE_MAGIC);
-                tree = smb_llist_next(tree_list, tree);
-        } else {
-                tree = smb_llist_head(tree_list);
-        }
+        for (tree = smb_llist_head(ll);
+            tree != NULL;
+            tree = smb_llist_next(ll, tree)) {
 
-        while (tree) {
-                if (smb_tree_hold(tree))
-                        break;
+                SMB_TREE_VALID(tree);
+                ASSERT(tree->t_session == session);
 
-                tree = smb_llist_next(tree_list, tree);
+                if (smb_strcasecmp(tree->t_sharename, sharename, 0) != 0)
+                        continue;
+
+                if (smb_tree_hold(tree)) {
+                        smb_llist_post(ll, tree,
+                            smb_session_tree_kill);
         }
+        }
 
-        smb_llist_exit(tree_list);
-        return (tree);
+        smb_llist_exit(ll);
 }
 
 /*
  * Logoff all users associated with the specified session.
+ *
+ * This is called for both server-initiated disconnect
+ * (SMB_SESSION_STATE_TERMINATED) and client-initiated
+ * disconnect (SMB_SESSION_STATE_DISCONNECTED).
+ * If client-initiated, save durable handles.
  */
-static void
+void
 smb_session_logoff(smb_session_t *session)
 {
+        smb_llist_t     *ulist;
         smb_user_t      *user;
 
         SMB_SESSION_VALID(session);
 
-        smb_session_disconnect_trees(session);
+top:
+        ulist = &session->s_user_list;
+        smb_llist_enter(ulist, RW_READER);
 
-        smb_llist_enter(&session->s_user_list, RW_READER);
-
-        user = smb_llist_head(&session->s_user_list);
+        user = smb_llist_head(ulist);
         while (user) {
                 SMB_USER_VALID(user);
                 ASSERT(user->u_session == session);
 
+                mutex_enter(&user->u_mutex);
                 switch (user->u_state) {
                 case SMB_USER_STATE_LOGGING_ON:
                 case SMB_USER_STATE_LOGGED_ON:
-                        smb_user_hold_internal(user);
+                        // smb_user_hold_internal(user);
+                        user->u_refcnt++;
+                        mutex_exit(&user->u_mutex);
                         smb_user_logoff(user);
                         smb_user_release(user);
                         break;
 
                 case SMB_USER_STATE_LOGGED_OFF:
                 case SMB_USER_STATE_LOGGING_OFF:
+                        mutex_exit(&user->u_mutex);
                         break;
 
                 default:
+                        mutex_exit(&user->u_mutex);
                         ASSERT(0);
                         break;
                 }
 
-                user = smb_llist_next(&session->s_user_list, user);
+                user = smb_llist_next(ulist, user);
         }
 
-        smb_llist_exit(&session->s_user_list);
+        /* Needed below (Was the list empty?) */
+        user = smb_llist_head(ulist);
+
+        smb_llist_exit(ulist);
+
+        /*
+         * It's possible for user objects to remain due to references
+         * obtained via smb_server_lookup_ssnid(), when an SMB2
+         * session setup is destroying a previous session.
+         *
+         * Wait for user objects to clear out (last refs. go away,
+         * then smb_user_delete takes them out of the list).  When
+         * the last user object is removed, the session state is
+         * set to SHUTDOWN and s_lock is signaled.
+         *
+         * Not all places that call smb_user_release necessarily
+         * flush the delete queue, so after we wait for the list
+         * to empty out, go back to the top and recheck the list
+         * delete queue to make sure smb_user_delete happens.
+         */
+        if (user == NULL) {
+                /* User list is empty. */
+                smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+                session->s_state = SMB_SESSION_STATE_SHUTDOWN;
+                smb_rwx_rwexit(&session->s_lock);
+        } else {
+                smb_rwx_rwenter(&session->s_lock, RW_READER);
+                if (session->s_state != SMB_SESSION_STATE_SHUTDOWN) {
+                        (void) smb_rwx_cvwait(&session->s_lock,
+                            MSEC_TO_TICK(200));
+                        smb_rwx_rwexit(&session->s_lock);
+                        goto top;
+                }
+                smb_rwx_rwexit(&session->s_lock);
+        }
+        ASSERT(session->s_state == SMB_SESSION_STATE_SHUTDOWN);
+
+        /*
+         * User list should be empty now.
+         */
+#ifdef  DEBUG
+        if (ulist->ll_count != 0) {
+                cmn_err(CE_WARN, "user list not empty?");
+                debug_enter("s_user_list");
+        }
+#endif
+
+        /*
+         * User logoff happens first so we'll set preserve_opens
+         * for client-initiated disconnect.  When that's done
+         * there should be no trees left, but check anyway.
+         */
+        smb_session_disconnect_trees(session);
 }
 
 /*
  * Copy the session workstation/client name to buf.  If the workstation
  * is an empty string (which it will be on TCP connections), use the

@@ -1318,11 +1436,12 @@
  * smb_request_alloc
  *
  * Allocate an smb_request_t structure from the kmem_cache.  Partially
  * initialize the found/new request.
  *
- * Returns pointer to a request
+ * Returns pointer to a request, or NULL if the session state is
+ * one in which new requests are no longer allowed.
  */
 smb_request_t *
 smb_request_alloc(smb_session_t *session, int req_length)
 {
         smb_request_t   *sr;

@@ -1338,11 +1457,10 @@
          * whole thing and start over.
          */
         bzero(sr, sizeof (smb_request_t));
 
         mutex_init(&sr->sr_mutex, NULL, MUTEX_DEFAULT, NULL);
-        cv_init(&sr->sr_ncr.nc_cv, NULL, CV_DEFAULT, NULL);
         smb_srm_init(sr);
         sr->session = session;
         sr->sr_server = session->s_server;
         sr->sr_gmtoff = session->s_server->si_gmtoff;
         sr->sr_cfg = &session->s_cfg;

@@ -1351,11 +1469,41 @@
         sr->sr_req_length = req_length;
         if (req_length)
                 sr->sr_request_buf = kmem_alloc(req_length, KM_SLEEP);
         sr->sr_magic = SMB_REQ_MAGIC;
         sr->sr_state = SMB_REQ_STATE_INITIALIZING;
+
+        /*
+         * Only allow new SMB requests in some states.
+         */
+        smb_rwx_rwenter(&session->s_lock, RW_WRITER);
+        switch (session->s_state) {
+        case SMB_SESSION_STATE_CONNECTED:
+        case SMB_SESSION_STATE_INITIALIZED:
+        case SMB_SESSION_STATE_ESTABLISHED:
+        case SMB_SESSION_STATE_NEGOTIATED:
         smb_slist_insert_tail(&session->s_req_list, sr);
+                break;
+
+        default:
+                ASSERT(0);
+                /* FALLTHROUGH */
+        case SMB_SESSION_STATE_DISCONNECTED:
+        case SMB_SESSION_STATE_SHUTDOWN:
+        case SMB_SESSION_STATE_TERMINATED:
+                /* Disallow new requests in these states. */
+                if (sr->sr_request_buf)
+                        kmem_free(sr->sr_request_buf, sr->sr_req_length);
+                sr->session = NULL;
+                sr->sr_magic = 0;
+                mutex_destroy(&sr->sr_mutex);
+                kmem_cache_free(smb_cache_request, sr);
+                sr = NULL;
+                break;
+        }
+        smb_rwx_rwexit(&session->s_lock);
+
         return (sr);
 }
 
 /*
  * smb_request_free

@@ -1366,23 +1514,30 @@
 smb_request_free(smb_request_t *sr)
 {
         ASSERT(sr->sr_magic == SMB_REQ_MAGIC);
         ASSERT(sr->session);
         ASSERT(sr->r_xa == NULL);
-        ASSERT(sr->sr_ncr.nc_fname == NULL);
 
         if (sr->fid_ofile != NULL) {
-                smb_ofile_request_complete(sr->fid_ofile);
                 smb_ofile_release(sr->fid_ofile);
         }
 
         if (sr->tid_tree != NULL)
                 smb_tree_release(sr->tid_tree);
 
         if (sr->uid_user != NULL)
                 smb_user_release(sr->uid_user);
 
+        if (sr->tform_ssn != NULL)
+                smb_user_release(sr->tform_ssn);
+
+        /*
+         * The above may have left work on the delete queues
+         */
+        smb_llist_flush(&sr->session->s_tree_list);
+        smb_llist_flush(&sr->session->s_user_list);
+
         smb_slist_remove(&sr->session->s_req_list, sr);
 
         sr->session = NULL;
 
         smb_srm_fini(sr);

@@ -1395,11 +1550,10 @@
                 m_freem(sr->reply.chain);
         if (sr->raw_data.chain)
                 m_freem(sr->raw_data.chain);
 
         sr->sr_magic = 0;
-        cv_destroy(&sr->sr_ncr.nc_cv);
         mutex_destroy(&sr->sr_mutex);
         kmem_cache_free(smb_cache_request, sr);
 }
 
 boolean_t

@@ -1415,49 +1569,17 @@
 boolean_t
 smb_session_levelII_oplocks(smb_session_t *session)
 {
         SMB_SESSION_VALID(session);
 
-        /* Clients using SMB2 and later always know about oplocks. */
-        if (session->dialect > NT_LM_0_12)
-                return (B_TRUE);
-
         /* Older clients only do Level II oplocks if negotiated. */
         if ((session->capabilities & CAP_LEVEL_II_OPLOCKS) != 0)
                 return (B_TRUE);
 
         return (B_FALSE);
 }
 
-/*
- * smb_session_oplock_break
- *
- * Send an oplock break request to the client,
- * recalling some cache delegation.
- */
-void
-smb_session_oplock_break(smb_request_t *sr, uint8_t brk)
-{
-        smb_session_t   *session = sr->session;
-        mbuf_chain_t    *mbc = &sr->reply;
-
-        SMB_SESSION_VALID(session);
-
-        /*
-         * Build the break message in sr->reply and then send it.
-         * The mbc is free'd later, in smb_request_free().
-         */
-        mbc->max_bytes = MLEN;
-        if (session->dialect <= NT_LM_0_12) {
-                smb1_oplock_break_notification(sr, brk);
-        } else {
-                smb2_oplock_break_notification(sr, brk);
-        }
-
-        (void) smb_session_send(session, 0, mbc);
-}
-
 static void
 smb_session_genkey(smb_session_t *session)
 {
         uint8_t         tmp_key[SMB_CHALLENGE_SZ];