Print this page
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-16943 network outages with thread stuck in smb2_scoreboard_cancel
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Evan Layton <evan.layton@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-5845 rework SMB immediate cancel
Reviewed by: Matt Barden <matt.barden@nexenta.com>
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
NEX-2975 SMB2 Cancel may fail (nits)
NEX-2975 SMB2 Cancel may fail
Reviewed by: Kevin Crowe <kevin.crowe@nexenta.com>
Reviewed by: Tony Nguyen <tony.nguyen@nexenta.com>
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)
        
@@ -8,74 +8,143 @@
  * source.  A copy of the CDDL is also available via the Internet at
  * http://www.illumos.org/license/CDDL.
  */
 
 /*
- * Copyright 2014 Nexenta Systems, Inc.  All rights reserved.
+ * Copyright 2018 Nexenta Systems, Inc.  All rights reserved.
  */
 
 /*
  * Dispatch function for SMB2_CANCEL
  */
 
 #include <smbsrv/smb2_kproto.h>
 
-static void smb2sr_cancel_async(smb_request_t *);
-static void smb2sr_cancel_sync(smb_request_t *);
+static void smb2_cancel_async(smb_request_t *);
+static void smb2_cancel_sync(smb_request_t *);
 
 /*
  * This handles an SMB2_CANCEL request when seen in the reader.
  * (See smb2sr_newrq)  Handle this immediately, rather than
  * going through the normal taskq dispatch mechanism.
  * Note that Cancel does NOT get a response.
+ *
+ * Any non-zero return causes disconnect.
+ * SMB2 header is already decoded.
  */
 int
-smb2sr_newrq_cancel(smb_request_t *sr)
+smb2_newrq_cancel(smb_request_t *sr)
 {
-        int rc;
 
         /*
-         * Decode the header
+         * If we get SMB2 cancel as part of a compound,
+         * that's a protocol violation.  Drop 'em!
          */
-        if ((rc = smb2_decode_header(sr)) != 0)
-                return (rc);
+        if (sr->smb2_next_command != 0)
+                return (EINVAL);
 
+        DTRACE_SMB2_START(op__Cancel, smb_request_t *, sr);
+
         if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND)
-                smb2sr_cancel_async(sr);
+                smb2_cancel_async(sr);
         else
-                smb2sr_cancel_sync(sr);
+                smb2_cancel_sync(sr);
 
+        DTRACE_SMB2_DONE(op__Cancel, smb_request_t *, sr);
+
         return (0);
 }
 
+/*
+ * Dispatch handler for SMB2_CANCEL.
+ * Note that Cancel does NOT get a response.
+ */
+smb_sdrc_t
+smb2_cancel(smb_request_t *sr)
+{
+
+        /*
+         * If we get SMB2 cancel as part of a compound,
+         * that's a protocol violation.  Drop 'em!
+         */
+        if (sr->smb2_cmd_hdr != 0 || sr->smb2_next_command != 0)
+                return (SDRC_DROP_VC);
+
+        DTRACE_SMB2_START(op__Cancel, smb_request_t *, sr);
+
+        if (sr->smb2_hdr_flags & SMB2_FLAGS_ASYNC_COMMAND) {
+                smb2_cancel_async(sr);
+        } else {
+                smb2_cancel_sync(sr);
+        }
+
+        DTRACE_SMB2_DONE(op__Cancel, smb_request_t *, sr);
+
+        return (SDRC_NO_REPLY);
+}
+
+/*
+ * SMB2 Cancel (sync) has an inherent race with the request being
+ * cancelled.  The request may have been received but not yet
+ * executed by a worker thread, in which case we'll mark the
+ * request state as cancelled, and when a worker thread starts
+ * on this request we'll cancel everything in the compound.
+ */
 static void
-smb2sr_cancel_sync(smb_request_t *sr)
+smb2_cancel_sync(smb_request_t *sr)
 {
         struct smb_request *req;
         struct smb_session *session = sr->session;
         int cnt = 0;
 
+        if (sr->smb2_messageid == 0)
+                goto failure;
+
         smb_slist_enter(&session->s_req_list);
-        req = smb_slist_head(&session->s_req_list);
-        while (req) {
-                ASSERT(req->sr_magic == SMB_REQ_MAGIC);
-                if ((req != sr) &&
-                    (req->smb2_messageid == sr->smb2_messageid)) {
+        for (req = smb_slist_head(&session->s_req_list); req != NULL;
+            req = smb_slist_next(&session->s_req_list, req)) {
+
+                /* never cancel self */
+                if (req == sr)
+                        continue;
+
+                if (sr->smb2_messageid >= req->smb2_first_msgid &&
+                    sr->smb2_messageid < (req->smb2_first_msgid +
+                    req->smb2_total_credits)) {
                         smb_request_cancel(req);
                         cnt++;
                 }
-                req = smb_slist_next(&session->s_req_list, req);
         }
+        smb_slist_exit(&session->s_req_list);
+
         if (cnt != 1) {
+        failure:
                 DTRACE_PROBE2(smb2__cancel__error,
                     uint64_t, sr->smb2_messageid, int, cnt);
+#ifdef  DEBUG
+                /*
+                 * It's somewhat common that we may see a cancel for a
+                 * request that has already completed, so report that
+                 * only in debug builds.
+                 */
+                cmn_err(CE_WARN, "SMB2 cancel failed, "
+                    "client=%s, MID=0x%llx",
+                    sr->session->ip_addr_str,
+                    (u_longlong_t)sr->smb2_messageid);
+#endif
         }
-        smb_slist_exit(&session->s_req_list);
 }
 
+/*
+ * Note that cancelling an async request doesn't have a race
+ * because the client doesn't learn about the async ID until we
+ * send it to them in an interim reply, and by that point the
+ * request has progressed to the point where smb_cancel can find
+ * the request and cancel it.
+ */
 static void
-smb2sr_cancel_async(smb_request_t *sr)
+smb2_cancel_async(smb_request_t *sr)
 {
         struct smb_request *req;
         struct smb_session *session = sr->session;
         int cnt = 0;
 
@@ -91,8 +160,13 @@
                 req = smb_slist_next(&session->s_req_list, req);
         }
         if (cnt != 1) {
                 DTRACE_PROBE2(smb2__cancel__error,
                     uint64_t, sr->smb2_async_id, int, cnt);
+                /*
+                 * Not logging here, as this is normal, i.e.
+                 * when both a cancel and a handle close
+                 * terminates an SMB2_notify request.
+                 */
         }
         smb_slist_exit(&session->s_req_list);
 }