}
smb2_deltree(tree, BASEDIR);
talloc_free(tree);
+
+ talloc_free(mem_ctx);
+
+ return ret;
+}
+
+/**
+ * Test Write Replay Fencing when there's an outstanding Write
+ */
+struct test_replay8_state {
+ struct torture_context *tctx;
+ uint16_t channel_seq;
+ struct smb2_tree *tree;
+ struct test_replay8_request {
+ struct test_replay8_state *state;
+ size_t idx;
+ struct smb2_write wr;
+ struct smb2_session *session;
+ struct smb2_request *req;
+ uint16_t channel_seq;
+ bool replay;
+ NTSTATUS status;
+ bool done;
+ } reqs[65];
+ size_t num_replies;
+};
+
+static void test_replay8_callback(struct smb2_request *req)
+{
+ struct test_replay8_request *r =
+ (struct test_replay8_request *)req->async.private_data;
+
+ r->state->num_replies++;
+ r->done = true;
+ r->status = smb2_write_recv(req, &r->wr);
+ torture_comment(r->state->tctx, "%s: Write %ju returned status %s\n",
+ __func__, r->idx, nt_errstr(r->status));
+}
+
+static bool test_replay8(struct torture_context *tctx, struct smb2_tree *tree1)
+{
+ const char *host = torture_setting_string(tctx, "host", NULL);
+ const char *share = torture_setting_string(tctx, "share", NULL);
+ struct cli_credentials *credentials = popt_get_cmdline_credentials();
+ NTSTATUS status;
+ TALLOC_CTX *mem_ctx = talloc_new(tctx);
+ struct smb2_handle _h1;
+ struct smb2_handle *h1 = NULL;
+ struct smb2_create io;
+ struct GUID create_guid = GUID_random();
+ const int wr_len = 60*1024;
+ struct smb2_write wr;
+ size_t i;
+ bool ret = true;
+ const char *fname = BASEDIR "\\replay8.dat";
+ struct smb2_tree *tree2 = NULL;
+ struct smb2_transport *transport1 = tree1->session->transport;
+ struct smb2_transport *transport2 = NULL;
+ struct smb2_session *session1_1 = tree1->session;
+ struct smb2_session *session1_2 = NULL;
+ struct test_replay8_state state = {
+ .tctx = tctx,
+ .channel_seq = smb2cli_session_reset_channel_sequence(session1_1->smbXcli, 0),
+ .tree = tree1,
+ };
+
+ if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) {
+ torture_skip(tctx, "SMB 3.X Dialect family required for Replay tests\n");
+ }
+
+ ZERO_STRUCT(break_info);
+ transport1->oplock.handler = torture_oplock_ack_handler;
+ transport1->oplock.private_data = tree1;
+
+ smb2cli_conn_set_max_credits(transport1->conn, UINT16_MAX);
+
+ smb2cli_session_reset_channel_sequence(session1_1->smbXcli, state.channel_seq);
+
+ torture_comment(tctx, "Write Replay fencing for Multi Channel\n");
+
+ status = torture_smb2_testdir(tree1, BASEDIR, &_h1);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ smb2_util_close(tree1, _h1);
+ smb2_util_unlink(tree1, fname);
+
+ /*
+ * use the 1st channel, 1st session
+ */
+ smb2_oplock_create_share(&io, fname,
+ smb2_util_share_access(""),
+ 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;
+
+ tree1->session = session1_1;
+ status = smb2_create(tree1, mem_ctx, &io);
+ CHECK_STATUS(status, NT_STATUS_OK);
+ _h1 = io.out.file.handle;
+ h1 = &_h1;
+ 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);
+ CHECK_VAL(io.out.timeout, io.in.timeout);
+
+ status = smb2_connect(tctx,
+ host,
+ lpcfg_smb_ports(tctx->lp_ctx),
+ share,
+ lpcfg_resolve_context(tctx->lp_ctx),
+ credentials,
+ &tree2,
+ tctx->ev,
+ &transport1->options,
+ lpcfg_socket_options(tctx->lp_ctx),
+ lpcfg_gensec_settings(tctx, tctx->lp_ctx)
+ );
+ torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
+ "smb2_connect failed");
+ transport2 = tree2->session->transport;
+
+ smb2cli_conn_set_max_credits(transport2->conn, UINT16_MAX);
+
+ /*
+ * Now bind the 1st session to 2nd transport channel
+ */
+ session1_2 = smb2_session_channel(transport2,
+ lpcfg_gensec_settings(tctx, tctx->lp_ctx),
+ tree2, session1_1);
+ torture_assert(tctx, session1_2 != NULL, "smb2_session_channel failed");
+
+ status = smb2_session_setup_spnego(session1_2,
+ popt_get_cmdline_credentials(),
+ 0 /* previous_session_id */);
+ CHECK_STATUS(status, NT_STATUS_OK);
+
+ tree1->session = session1_1;
+ smb2_util_unlink(tree1, "__none__");
+ smb2_util_unlink(tree1, "__none__");
+ smb2_util_unlink(tree1, "__none__");
+ smb2_util_unlink(tree1, "__none__");
+ tree1->session = session1_2;
+ smb2_util_unlink(tree1, "__none__");
+ smb2_util_unlink(tree1, "__none__");
+ smb2_util_unlink(tree1, "__none__");
+ smb2_util_unlink(tree1, "__none__");
+ tree1->session = session1_1;
+
+ wr = (struct smb2_write) {
+ .in.file.handle = *h1,
+ .in.offset = 0,
+ .in.data = data_blob_talloc(mem_ctx, NULL, wr_len)
+ };
+
+ if (wr.in.data.data == NULL) {
+ torture_comment(tctx, "talloc failed\n");
+ }
+
+ torture_comment(tctx, "METZE:%s\n", __location__);
+ for (i=0; i < ARRAY_SIZE(state.reqs)/2; i++) {
+ struct test_replay8_request *r = &state.reqs[i];
+ /*
+ * Queue multiple writes on 1st Channel
+ */
+ r->idx = i;
+ r->state = &state;
+ r->wr = wr;
+ r->session = session1_1;
+ r->channel_seq = r->state->channel_seq;
+ r->replay = false;
+ }
+ for (; i < (ARRAY_SIZE(state.reqs)/2)+1; i++) {
+ struct test_replay8_request *r = &state.reqs[i];
+ /*
+ * Queue one write with channel_seq + 1 on 2nd Channel
+ */
+ r->idx = i;
+ r->state = &state;
+ r->wr = wr;
+ r->session = session1_1;
+ r->channel_seq = r->state->channel_seq + 1;
+ r->replay = false;
+ }
+ for (; i < ARRAY_SIZE(state.reqs); i++) {
+ struct test_replay8_request *r = &state.reqs[i];
+ /*
+ * Queue multiple writes on 1st Channel
+ */
+ r->idx = i;
+ r->state = &state;
+ r->wr = wr;
+ r->session = session1_1;
+ r->channel_seq = r->state->channel_seq;
+ r->replay = false;
+ }
+
+ for (i=0; i < ARRAY_SIZE(state.reqs); i++) {
+ struct test_replay8_request *r = &state.reqs[i];
+
+ torture_comment(tctx, "Start %ju chseq[%u/%p] replay[%u]\n",
+ i, r->channel_seq, r->session, r->replay);
+
+ r->state->tree->session = r->session;
+ smb2cli_session_reset_channel_sequence(r->session->smbXcli,
+ r->channel_seq);
+ if (r->replay) {
+ smb2cli_session_start_replay(r->session->smbXcli);
+ }
+
+ r->req = smb2_write_send(tree1, &r->wr);
+ r->req->async.fn = test_replay8_callback;
+ r->req->async.private_data = r;
+
+ smb2cli_session_reset_channel_sequence(r->session->smbXcli,
+ r->state->channel_seq);
+ smb2cli_session_stop_replay(r->session->smbXcli);
+ }
+
+ tree1->session = session1_1;
+
+ torture_comment(tctx, "METZE:%s\n", __location__);
+ while (state.num_replies < ARRAY_SIZE(state.reqs)) {
+ if (!smbXcli_conn_is_connected(transport1->conn)) {
+ torture_comment(tctx, "transport1 disconnected\n");
+ ret = false;
+ goto done;
+ }
+
+ if (!smbXcli_conn_is_connected(transport2->conn)) {
+ torture_comment(tctx, "transport2 disconnected\n");
+ ret = false;
+ goto done;
+ }
+
+ if (tevent_loop_once(tctx->ev) != 0) {
+ torture_comment(tctx, "tevent_loop_once failed\n");
+ ret = false;
+ goto done;
+ }
+ }
+
+ torture_comment(tctx, "METZE:%s\n", __location__);
+
+ for (i=0; i < ARRAY_SIZE(state.reqs); i++) {
+ struct test_replay8_request *r = &state.reqs[i];
+
+ torture_comment(tctx, "Write %ju returned done[%d] status %s\n",
+ i, r->done, nt_errstr(r->status));
+ }
+
+ tree1->session = session1_1;
+ smb2_util_close(tree1, *h1);
+ h1 = NULL;
+
+done:
+ talloc_free(tree2);
+ tree1->session = session1_1;
+
+ if (h1 != NULL) {
+ smb2_util_close(tree1, *h1);
+ }
+
+ smb2_util_unlink(tree1, fname);
+ smb2_deltree(tree1, BASEDIR);
+
+ talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
torture_suite_add_1smb2_test(suite, "replay5", test_replay5);
torture_suite_add_1smb2_test(suite, "replay6", test_replay6);
torture_suite_add_1smb2_test(suite, "replay7", test_replay7);
+ torture_suite_add_1smb2_test(suite, "replay8", test_replay8);
suite->description = talloc_strdup(suite, "SMB2 REPLAY tests");