From: Ralph Boehme Date: Mon, 25 Aug 2025 09:38:47 +0000 (+0200) Subject: smbtorture: add test "smb2.persistent-open.reconnect-contended-replay" X-Git-Url: http://git.ipfire.org/cgi-bin/gitweb.cgi?a=commitdiff_plain;h=1c03ed45c65f130fd56beed692c661cce9796b02;p=thirdparty%2Fsamba.git smbtorture: add test "smb2.persistent-open.reconnect-contended-replay" This tests verifies, that a * contending open on a disconnected persisten open that triggers a lease break is blocked, * a replay if the create that caused the lease break fails with NT_STATUS_FILE_NOT_AVAILABLE as long the the persistent open is still disconnected, * after the persistent open is reconnected the second replay of the contending open succeeds * the client with the persistent open receives a lease break Signed-off-by: Ralph Boehme Reviewed-by: Anoop C S --- diff --git a/source4/torture/smb2/durable_v2_open.c b/source4/torture/smb2/durable_v2_open.c index 9e584abe766..3ddcd13f73c 100644 --- a/source4/torture/smb2/durable_v2_open.c +++ b/source4/torture/smb2/durable_v2_open.c @@ -7786,6 +7786,244 @@ static bool test_persistent_reconnect_contended_oplock( tctx, _tree, table); } +/* + * This tests verifies, that a + * + * - contending open on a disconnected persistent open that triggers a lease + * break is blocked, + * + * - a replay of the create that caused the lease break fails with + * NT_STATUS_FILE_NOT_AVAILABLE as long the the persistent open is still + * disconnected, + * + * - after the persistent open is reconnected the second replay of the + * contending open succeeds + * + * - the client with the persistent open receives a lease break + */ +static bool test_persistent_reconnect_contended_replay( + struct torture_context *tctx, + struct smb2_tree *_tree) +{ + TALLOC_CTX *mem_ctx = talloc_new(tctx); + struct smb2_tree *tree1 = NULL; + struct smb2_tree *tree2 = NULL; + struct smb2_request *req = NULL; + struct smb2_create io1; + struct smb2_create io2; + struct smb2_lease ls1; + struct smb2_handle h1 = {0}; + struct smb2_handle h2 = {0}; + uint32_t caps; + uint32_t tcon_caps; + struct smbcli_options options1 = _tree->session->transport->options; + struct smbcli_options options2 = _tree->session->transport->options; + uint64_t lease1 = random(); + uint64_t lease2 = random(); + struct GUID create_guid1 = GUID_random(); + struct GUID create_guid2 = GUID_random(); + char *fname = NULL; + int rc; + NTSTATUS status; + bool ret = true; + + caps = smb2cli_conn_server_capabilities(_tree->session->transport->conn); + if (!(caps & SMB2_CAP_LEASING)) { + torture_skip(tctx, "leases are not supported"); + } + + tcon_caps = smb2cli_tcon_capabilities(_tree->smbXcli); + if (!(tcon_caps & SMB2_SHARE_CAP_CONTINUOUS_AVAILABILITY)) { + torture_skip(tctx, "PH are not supported\n"); + } + + if (tcon_caps & SMB2_SHARE_CAP_SCALEOUT) { + torture_skip(tctx, + "SO share, test requires RWH lease, skipping\n"); + } + + fname = talloc_asprintf(tctx, "lease_break-%ld.dat", random()); + torture_assert_not_null_goto(tctx, fname, ret, done, + "talloc_asprintf failed\n"); + + options1.client_guid = GUID_random(); + options1.request_timeout = 240; + options2.client_guid = GUID_random(); + options2.request_timeout = 240; + + ret = torture_smb2_connection_ext(tctx, 0, &options1, &tree1); + torture_assert_goto(tctx, ret, ret, done, + "torture_smb2_connection_ext failed\n"); + + ret = torture_smb2_connection_ext(tctx, 0, &options2, &tree2); + torture_assert_goto(tctx, ret, ret, done, + "torture_smb2_connection_ext failed\n"); + + /* + * Get persistent open + */ + smb2_lease_v2_create_share(&io1, &ls1, false, fname, + smb2_util_share_access("RWD"), + lease1, NULL, + smb2_util_lease_state("RWH"), 1); + io1.in.durable_open_v2 = true; + io1.in.persistent_open = true; + io1.in.create_guid = create_guid1; + status = smb2_create(tree1, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = io1.out.file.handle; + torture_assert_goto(tctx, io1.out.persistent_open == true, + ret, done, "Persistent open not granted\n"); + + torture_assert_int_equal_goto(tctx, + io1.out.oplock_level, + SMB2_OPLOCK_LEVEL_LEASE, + ret, done, + "Bad oplock level\n"); + torture_assert_int_equal_goto(tctx, + io1.out.lease_response_v2.lease_state, + smb2_util_lease_state("RWH"), + ret, done, + "Bad lease state\n"); + + TALLOC_FREE(tree1); + sleep(1); + + /* + * Contend disconnected durable open with second open from second client + */ + smb2_lease_v2_create(&io2, NULL, false, fname, lease2, NULL, + smb2_util_lease_state("none"), 1); + io2.in.durable_open_v2 = true; + io2.in.create_guid = create_guid2; + req = smb2_create_send(tree2, &io2); + torture_assert_not_null_goto(tctx, req, ret, done, + "smb2_create_send failed\n"); + + /* + * Wait til the interim response for the create arrives and then + * disconnect + */ + while (!req->cancel.can_cancel) { + rc = tevent_loop_once(req->transport->ev); + torture_assert_goto(tctx, rc == 0, ret, done, + "tevent_loop_once failed\n"); + } + + TALLOC_FREE(req); + TALLOC_FREE(tree2); + sleep(1); + + /* + * Reconnect second client and send a create replay. As the first create + * did not finish before we disconnected, there is no open on the server + * and no replay cache entry, so this second create is handled as a + * "normal" create, again getting STATUS_PENDING. + */ + + ret = torture_smb2_connection_ext(tctx, 0, + &options2, &tree2); + torture_assert_goto(tctx, ret, ret, done, + "torture_smb2_connection_ext failed\n"); + + smb2cli_session_start_replay(tree2->session->smbXcli); + + req = smb2_create_send(tree2, &io2); + torture_assert_not_null_goto(tctx, req, ret, done, + "smb2_create_send failed\n"); + smb2cli_session_stop_replay(tree2->session->smbXcli); + + /* + * Wait til the interim response for the create arrives and then + * disconnect + */ + while (!req->cancel.can_cancel) { + rc = tevent_loop_once(req->transport->ev); + torture_assert_goto(tctx, rc == 0, ret, done, + "tevent_loop_once failed\n"); + } + TALLOC_FREE(req); + TALLOC_FREE(tree2); + + /* + * Now reconnect the first client and reconnect the persistent open + */ + ret = torture_smb2_connection_ext(tctx, 0, &options1, &tree1); + torture_assert_goto(tctx, ret, ret, done, + "torture_smb2_connection_ext failed\n"); + torture_reset_lease_break_info(tctx, &lease_break_info); + tree1->session->transport->lease.handler = torture_lease_handler; + tree1->session->transport->lease.private_data = tree1; + + ZERO_STRUCT(io1); + smb2_lease_v2_create(&io1, &ls1, false, fname, lease1, NULL, + smb2_util_lease_state("RWH"), 1); + io1.in.durable_handle_v2 = &h1; + io1.in.create_guid = create_guid1; + io1.in.persistent_open = true; + status = smb2_create(tree1, mem_ctx, &io1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + h1 = io1.out.file.handle; + torture_assert_int_equal_goto(tctx, + io1.out.oplock_level, + SMB2_OPLOCK_LEVEL_LEASE, + ret, done, + "Bad lease level\n"); + + torture_wait_for_lease_break(tctx); + + CHECK_BREAK_INFO_V2(tree1->session->transport, + "RWH", "RH", + lease1, + ls1.lease_epoch+2); + + /* + * Second client replays open, passes now. Note that this will not have + * been a replay on the server, but there's no way to verify this, just + * expect success. + */ + + ret = torture_smb2_connection_ext(tctx, 0, &options2, &tree2); + torture_assert_goto(tctx, ret, ret, done, + "torture_smb2_connection_ext failed\n"); + + smb2cli_session_start_replay(tree2->session->smbXcli); + status = smb2_create(tree2, mem_ctx, &io2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_create failed\n"); + smb2cli_session_stop_replay(tree2->session->smbXcli); + h2 = io2.out.file.handle; + + status = smb2_util_close(tree1, h1); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h1); + + status = smb2_util_close(tree2, h2); + torture_assert_ntstatus_ok_goto(tctx, status, ret, done, + "smb2_util_close failed\n"); + ZERO_STRUCT(h2); + +done: + if (!smb2_util_handle_empty(h1) && (tree1 != NULL)) { + smb2_util_close(tree1, h1); + } + if (!smb2_util_handle_empty(h2) && (tree2 != NULL)) { + smb2_util_close(tree2, h2); + } + if (fname != NULL) { + smb2_util_unlink(_tree, fname); + TALLOC_FREE(fname); + } + + TALLOC_FREE(tree1); + TALLOC_FREE(tree2); + TALLOC_FREE(mem_ctx); + return ret; +} + struct torture_suite *torture_smb2_persistent_open_init(TALLOC_CTX *ctx) { struct torture_suite *suite = @@ -7801,6 +8039,7 @@ struct torture_suite *torture_smb2_persistent_open_init(TALLOC_CTX *ctx) torture_suite_add_1smb2_test(suite, "reconnect-contended-win-broken", test_persistent_reconnect_contended_win_broken); torture_suite_add_1smb2_test(suite, "reconnect-contended-two", test_persistent_reconnect_contended_two); torture_suite_add_1smb2_test(suite, "reconnect-contended-oplock", test_persistent_reconnect_contended_oplock); + torture_suite_add_1smb2_test(suite, "reconnect-contended-replay", test_persistent_reconnect_contended_replay); return suite; }