torture:smb2: Add test replay6 to verify Error Codes for DurableHandleReqV2 replay
authorAnubhav Rakshit <anubhav.rakshit@gmail.com>
Thu, 30 Oct 2014 07:50:57 +0000 (13:20 +0530)
committerJeremy Allison <jra@samba.org>
Mon, 21 Mar 2016 23:23:21 +0000 (00:23 +0100)
Pair-Programmed-With: Stefan Metzmacher <metze@samba.org>
Pair-Programmed-With: Guenther Deschner <gd@samba.org>
Pair-Programmed-With: Michael Adam <obnox@samba.org>

Signed-off-by: Anubhav Rakshit <anubhav.rakshit@gmail.com>
Signed-off-by: Stefan Metzmacher <metze@samba.org>
Signed-off-by: Guenther Deschner <gd@samba.org>
Signed-off-by: Michael Adam <obnox@samba.org>
Reviewed-by: Jeremy Allison <jra@samba.org>
source4/torture/smb2/replay.c

index 8388837dde48361df4261b82daae006d47d9e096..91bb56800fe0c2680532c837e67aed0f675dd5bc 100644 (file)
@@ -94,7 +94,7 @@
 
 #define BASEDIR "replaytestdir"
 
-static struct {
+struct break_info {
        struct torture_context *tctx;
        struct smb2_handle handle;
        uint8_t level;
@@ -102,7 +102,16 @@ static struct {
        int count;
        int failures;
        NTSTATUS failure_status;
-} break_info;
+};
+
+static struct break_info break_info;
+
+static void torture_reset_break_info(struct torture_context *tctx,
+                                    struct break_info *r)
+{
+       ZERO_STRUCTP(r);
+       r->tctx = tctx;
+}
 
 static void torture_oplock_ack_callback(struct smb2_request *req)
 {
@@ -162,6 +171,61 @@ static bool torture_oplock_ack_handler(struct smb2_transport *transport,
        return true;
 }
 
+/**
+ * Timer handler function notifies the registering function that time is up
+ */
+static void timeout_cb(struct tevent_context *ev,
+                      struct tevent_timer *te,
+                      struct timeval current_time,
+                      void *private_data)
+{
+       bool *timesup = (bool *)private_data;
+       *timesup = true;
+       return;
+}
+
+/**
+ *  Wait a short period of time to receive a single oplock break request
+ */
+static void torture_wait_for_oplock_break(struct torture_context *tctx)
+{
+       TALLOC_CTX *tmp_ctx = talloc_new(NULL);
+       struct tevent_timer *te = NULL;
+       struct timeval ne;
+       bool timesup = false;
+       int old_count = break_info.count;
+
+       /* Wait .1 seconds for an oplock break */
+       ne = tevent_timeval_current_ofs(0, 100000);
+
+       te = tevent_add_timer(tctx->ev, tmp_ctx, ne, timeout_cb, &timesup);
+       if (te == NULL) {
+               torture_comment(tctx, "Failed to wait for an oplock break. "
+                                     "test results may not be accurate.");
+               goto done;
+       }
+
+       while (!timesup && break_info.count < old_count + 1) {
+               if (tevent_loop_once(tctx->ev) != 0) {
+                       torture_comment(tctx, "Failed to wait for an oplock "
+                                             "break. test results may not be "
+                                             "accurate.");
+                       goto done;
+               }
+       }
+
+done:
+       /*
+        * We don't know if the timed event fired and was freed, we received
+        * our oplock break, or some other event triggered the loop.  Thus,
+        * we create a tmp_ctx to be able to safely free/remove the timed
+        * event in all 3 cases.
+        */
+       talloc_free(tmp_ctx);
+
+       return;
+}
+
 /**
  * Test what happens when SMB2_FLAGS_REPLAY_OPERATION is enabled for various
  * commands. We want to verify if the server returns an error code or not.
@@ -2221,6 +2285,148 @@ done:
        return ret;
 }
 
+
+/**
+ * Test Error Codes when a DurableHandleReqV2 with matching CreateGuid is
+ * re-sent with or without SMB2_FLAGS_REPLAY_OPERATION
+ */
+static bool test_replay6(struct torture_context *tctx, struct smb2_tree *tree)
+{
+       NTSTATUS status;
+       TALLOC_CTX *mem_ctx = talloc_new(tctx);
+       struct smb2_handle _h;
+       struct smb2_handle *h = NULL;
+       struct smb2_create io, ref1;
+       union smb_fileinfo qfinfo;
+       struct GUID create_guid = GUID_random();
+       bool ret = true;
+       const char *fname = BASEDIR "\\replay6.dat";
+       struct smb2_transport *transport = tree->session->transport;
+
+       if (smbXcli_conn_protocol(transport->conn) < PROTOCOL_SMB3_00) {
+               torture_skip(tctx, "SMB 3.X Dialect family required for "
+                                  "replay tests\n");
+       }
+
+       torture_reset_break_info(tctx, &break_info);
+       tree->session->transport->oplock.handler = torture_oplock_ack_handler;
+       tree->session->transport->oplock.private_data = tree;
+
+       torture_comment(tctx, "Error Codes for DurableHandleReqV2 Replay\n");
+       smb2_util_unlink(tree, fname);
+       status = torture_smb2_testdir(tree, BASEDIR, &_h);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       smb2_util_close(tree, _h);
+       torture_wait_for_oplock_break(tctx);
+       CHECK_VAL(break_info.count, 0);
+       torture_reset_break_info(tctx, &break_info);
+
+       smb2_oplock_create_share(&io, fname,
+                       smb2_util_share_access("RWD"),
+                       smb2_util_oplock_level("b"));
+       io.in.durable_open = false;
+       io.in.durable_open_v2 = true;
+       io.in.persistent_open = false;
+       io.in.create_guid = create_guid;
+       io.in.timeout = UINT32_MAX;
+
+       status = smb2_create(tree, mem_ctx, &io);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       ref1 = io;
+       _h = io.out.file.handle;
+       h = &_h;
+       CHECK_CREATED(&io, CREATED, FILE_ATTRIBUTE_ARCHIVE);
+       CHECK_VAL(io.out.oplock_level, smb2_util_oplock_level("b"));
+       CHECK_VAL(io.out.durable_open, false);
+       CHECK_VAL(io.out.durable_open_v2, true);
+
+       io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY;
+       io.in.create_disposition = NTCREATEX_DISP_OPEN;
+       smb2cli_session_start_replay(tree->session->smbXcli);
+       status = smb2_create(tree, mem_ctx, &io);
+       smb2cli_session_stop_replay(tree->session->smbXcli);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       CHECK_CREATE_OUT(&io, &ref1);
+       torture_wait_for_oplock_break(tctx);
+       CHECK_VAL(break_info.count, 0);
+       torture_reset_break_info(tctx, &break_info);
+
+       qfinfo = (union smb_fileinfo) {
+               .generic.level = RAW_SFILEINFO_POSITION_INFORMATION,
+               .generic.in.file.handle = *h
+       };
+       torture_comment(tctx, "Trying getinfo\n");
+       status = smb2_getinfo_file(tree, mem_ctx, &qfinfo);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       CHECK_VAL(qfinfo.position_information.out.position, 0);
+
+       smb2cli_session_start_replay(tree->session->smbXcli);
+       status = smb2_create(tree, mem_ctx, &io);
+       smb2cli_session_stop_replay(tree->session->smbXcli);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       torture_assert_u64_not_equal_goto(tctx,
+               io.out.file.handle.data[0],
+               ref1.out.file.handle.data[0],
+               ret, done, "data 0");
+       torture_assert_u64_not_equal_goto(tctx,
+               io.out.file.handle.data[1],
+               ref1.out.file.handle.data[1],
+               ret, done, "data 1");
+       torture_wait_for_oplock_break(tctx);
+       CHECK_VAL(break_info.count, 1);
+       CHECK_VAL(break_info.level, smb2_util_oplock_level("s"));
+       torture_reset_break_info(tctx, &break_info);
+
+       /*
+        * Resend the matching Durable V2 Create without
+        * SMB2_FLAGS_REPLAY_OPERATION. This triggers an oplock break and still
+        * gets NT_STATUS_DUPLICATE_OBJECTID
+        */
+       status = smb2_create(tree, mem_ctx, &io);
+       CHECK_STATUS(status, NT_STATUS_DUPLICATE_OBJECTID);
+       torture_wait_for_oplock_break(tctx);
+       CHECK_VAL(break_info.count, 0);
+       torture_reset_break_info(tctx, &break_info);
+
+       /*
+        * According to MS-SMB2 3.3.5.9.10 if Durable V2 Create is replayed and
+        * FileAttributes or CreateDisposition do not match the earlier Create
+        * request the Server fails request with
+        * NT_STATUS_INVALID_PARAMETER. But through this test we see that server
+        * does not really care about changed FileAttributes or
+        * CreateDisposition.
+        */
+       io.in.file_attributes = FILE_ATTRIBUTE_DIRECTORY;
+       io.in.create_disposition = NTCREATEX_DISP_OPEN;
+       smb2cli_session_start_replay(tree->session->smbXcli);
+       status = smb2_create(tree, mem_ctx, &io);
+       smb2cli_session_stop_replay(tree->session->smbXcli);
+       CHECK_STATUS(status, NT_STATUS_OK);
+       torture_assert_u64_not_equal_goto(tctx,
+               io.out.file.handle.data[0],
+               ref1.out.file.handle.data[0],
+               ret, done, "data 0");
+       torture_assert_u64_not_equal_goto(tctx,
+               io.out.file.handle.data[1],
+               ref1.out.file.handle.data[1],
+               ret, done, "data 1");
+       torture_wait_for_oplock_break(tctx);
+       CHECK_VAL(break_info.count, 0);
+
+done:
+       if (h != NULL) {
+               smb2_util_close(tree, *h);
+       }
+
+       smb2_util_unlink(tree, fname);
+       smb2_deltree(tree, BASEDIR);
+
+       talloc_free(tree);
+       talloc_free(mem_ctx);
+
+       return ret;
+}
+
 struct torture_suite *torture_smb2_replay_init(void)
 {
        struct torture_suite *suite =
@@ -2240,6 +2446,7 @@ struct torture_suite *torture_smb2_replay_init(void)
        torture_suite_add_1smb2_test(suite, "replay3", test_replay3);
        torture_suite_add_1smb2_test(suite, "replay4", test_replay4);
        torture_suite_add_1smb2_test(suite, "replay5", test_replay5);
+       torture_suite_add_1smb2_test(suite, "replay6", test_replay6);
 
        suite->description = talloc_strdup(suite, "SMB2 REPLAY tests");