1
0
mirror of https://github.com/samba-team/samba.git synced 2024-12-22 13:34:15 +03:00
samba-mirror/source4/torture/smb2/multichannel.c
Joseph Sutton 5c4ee07ea2 Revert "s4:torture: Increase multichannel timeout"
This reverts commit c6d0df787a.

Said commit did not help with GitLab CI timeouts, but just made the CI
pipeline take longer when the test did time out.

Signed-off-by: Joseph Sutton <josephsutton@catalyst.net.nz>
Reviewed-by: Andrew Bartlett <abartlet@samba.org>
2023-12-08 02:28:33 +00:00

2744 lines
85 KiB
C

/*
* Unix SMB/CIFS implementation.
*
* test SMB2 multichannel operations
*
* Copyright (C) Guenther Deschner, 2016
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; either version 3 of the License, or
* (at your option) any later version.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*/
#include "includes.h"
#include "libcli/smb2/smb2.h"
#include "libcli/smb2/smb2_calls.h"
#include "torture/torture.h"
#include "torture/smb2/proto.h"
#include "libcli/security/security.h"
#include "librpc/gen_ndr/ndr_security.h"
#include "librpc/gen_ndr/ndr_ioctl.h"
#include "../libcli/smb/smbXcli_base.h"
#include "lib/cmdline/cmdline.h"
#include "libcli/security/security.h"
#include "libcli/resolve/resolve.h"
#include "lib/socket/socket.h"
#include "lib/param/param.h"
#include "lib/events/events.h"
#include "oplock_break_handler.h"
#include "lease_break_handler.h"
#include "torture/smb2/block.h"
#define BASEDIR "multichanneltestdir"
#define CHECK_STATUS(status, correct) \
torture_assert_ntstatus_equal_goto(tctx, status, correct,\
ret, done, "")
#define CHECK_VAL(v, correct) do { \
if ((v) != (correct)) { \
torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s" \
" got 0x%x - should be 0x%x\n", \
__location__, #v, (int)v, (int)correct); \
ret = false; \
goto done; \
} } while (0)
#define CHECK_VAL_GREATER_THAN(v, gt_val) do { \
if ((v) <= (gt_val)) { \
torture_result(tctx, TORTURE_FAIL, \
"(%s): wrong value for %s got 0x%x - " \
"should be greater than 0x%x\n", \
__location__, #v, (int)v, (int)gt_val); \
ret = false; \
goto done; \
} } while (0)
#define CHECK_CREATED(__io, __created, __attribute) \
do { \
CHECK_VAL((__io)->out.create_action, \
NTCREATEX_ACTION_ ## __created); \
CHECK_VAL((__io)->out.size, 0); \
CHECK_VAL((__io)->out.file_attr, (__attribute)); \
CHECK_VAL((__io)->out.reserved2, 0); \
} while (0)
#define CHECK_PTR(ptr, correct) do { \
if ((ptr) != (correct)) { \
torture_result(tctx, TORTURE_FAIL, "(%s): wrong value for %s " \
"got 0x%p - should be 0x%p\n", \
__location__, #ptr, ptr, correct); \
ret = false; \
goto done; \
} } while (0)
#define CHECK_LEASE(__io, __state, __oplevel, __key, __flags) \
do { \
CHECK_VAL((__io)->out.lease_response.lease_version, 1); \
if (__oplevel) { \
CHECK_VAL((__io)->out.oplock_level, \
SMB2_OPLOCK_LEVEL_LEASE); \
CHECK_VAL((__io)->out.lease_response.lease_key.data[0],\
(__key)); \
CHECK_VAL((__io)->out.lease_response.lease_key.data[1],\
~(__key)); \
CHECK_VAL((__io)->out.lease_response.lease_state,\
smb2_util_lease_state(__state)); \
} else { \
CHECK_VAL((__io)->out.oplock_level,\
SMB2_OPLOCK_LEVEL_NONE); \
CHECK_VAL((__io)->out.lease_response.lease_key.data[0],\
0); \
CHECK_VAL((__io)->out.lease_response.lease_key.data[1],\
0); \
CHECK_VAL((__io)->out.lease_response.lease_state, 0); \
} \
\
CHECK_VAL((__io)->out.lease_response.lease_flags, (__flags)); \
CHECK_VAL((__io)->out.lease_response.lease_duration, 0); \
CHECK_VAL((__io)->out.lease_response.lease_epoch, 0); \
} while (0)
#define CHECK_LEASE_V2(__io, __state, __oplevel, __key, __flags, __parent, __epoch) \
do { \
CHECK_VAL((__io)->out.lease_response_v2.lease_version, 2); \
if (__oplevel) { \
CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_LEASE); \
CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], (__key)); \
CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], ~(__key)); \
CHECK_VAL((__io)->out.lease_response_v2.lease_state, smb2_util_lease_state(__state)); \
} else { \
CHECK_VAL((__io)->out.oplock_level, SMB2_OPLOCK_LEVEL_NONE); \
CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[0], 0); \
CHECK_VAL((__io)->out.lease_response_v2.lease_key.data[1], 0); \
CHECK_VAL((__io)->out.lease_response_v2.lease_state, 0); \
} \
\
CHECK_VAL((__io)->out.lease_response_v2.lease_flags, __flags); \
if (__flags & SMB2_LEASE_FLAG_PARENT_LEASE_KEY_SET) { \
CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[0], (__parent)); \
CHECK_VAL((__io)->out.lease_response_v2.parent_lease_key.data[1], ~(__parent)); \
} \
CHECK_VAL((__io)->out.lease_response_v2.lease_duration, 0); \
CHECK_VAL((__io)->out.lease_response_v2.lease_epoch, (__epoch)); \
} while(0)
#define CHECK_LEASE_BREAK_V2(__lb, __key, __from, __to, __break_flags, __new_epoch) \
do { \
CHECK_VAL((__lb).current_lease.lease_key.data[0], (__key)); \
CHECK_VAL((__lb).current_lease.lease_key.data[1], ~(__key)); \
CHECK_VAL((__lb).current_lease.lease_state, smb2_util_lease_state(__from)); \
CHECK_VAL((__lb).new_epoch, (__new_epoch)); \
CHECK_VAL((__lb).break_flags, (__break_flags)); \
CHECK_VAL((__lb).new_lease_state, smb2_util_lease_state(__to)); \
} while(0)
static bool test_ioctl_network_interface_info(struct torture_context *tctx,
struct smb2_tree *tree,
struct fsctl_net_iface_info *info)
{
union smb_ioctl ioctl;
struct smb2_handle fh;
uint32_t caps;
caps = smb2cli_conn_server_capabilities(tree->session->transport->conn);
if (!(caps & SMB2_CAP_MULTI_CHANNEL)) {
torture_skip(tctx,
"server doesn't support SMB2_CAP_MULTI_CHANNEL\n");
}
ZERO_STRUCT(ioctl);
ioctl.smb2.level = RAW_IOCTL_SMB2;
fh.data[0] = UINT64_MAX;
fh.data[1] = UINT64_MAX;
ioctl.smb2.in.file.handle = fh;
ioctl.smb2.in.function = FSCTL_QUERY_NETWORK_INTERFACE_INFO;
/* Windows client sets this to 64KiB */
ioctl.smb2.in.max_output_response = 0x10000;
ioctl.smb2.in.flags = SMB2_IOCTL_FLAG_IS_FSCTL;
torture_assert_ntstatus_ok(tctx,
smb2_ioctl(tree, tctx, &ioctl.smb2),
"FSCTL_QUERY_NETWORK_INTERFACE_INFO failed");
torture_assert(tctx,
(ioctl.smb2.out.out.length != 0),
"no interface info returned???");
torture_assert_ndr_success(tctx,
ndr_pull_struct_blob(&ioctl.smb2.out.out, tctx, info,
(ndr_pull_flags_fn_t)ndr_pull_fsctl_net_iface_info),
"failed to ndr pull");
if (DEBUGLVL(1)) {
NDR_PRINT_DEBUG(fsctl_net_iface_info, info);
}
return true;
}
static bool test_multichannel_interface_info(struct torture_context *tctx,
struct smb2_tree *tree)
{
struct fsctl_net_iface_info info;
return test_ioctl_network_interface_info(tctx, tree, &info);
}
static struct smb2_tree *test_multichannel_create_channel(
struct torture_context *tctx,
const char *host,
const char *share,
struct cli_credentials *credentials,
const struct smbcli_options *_transport_options,
struct smb2_tree *parent_tree
)
{
struct smbcli_options transport_options = *_transport_options;
NTSTATUS status;
struct smb2_transport *transport;
struct smb2_session *session;
bool ret = true;
struct smb2_tree *tree;
if (parent_tree) {
transport_options.only_negprot = true;
}
status = smb2_connect(tctx,
host,
lpcfg_smb_ports(tctx->lp_ctx),
share,
lpcfg_resolve_context(tctx->lp_ctx),
credentials,
&tree,
tctx->ev,
&transport_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");
transport = tree->session->transport;
transport->oplock.handler = torture_oplock_ack_handler;
transport->oplock.private_data = tree;
transport->lease.handler = torture_lease_handler;
transport->lease.private_data = tree;
torture_comment(tctx, "established transport [%p]\n", transport);
/*
* If parent tree is set, bind the session to the parent transport
*/
if (parent_tree) {
session = smb2_session_channel(transport,
lpcfg_gensec_settings(tctx, tctx->lp_ctx),
parent_tree, parent_tree->session);
torture_assert_goto(tctx, session != NULL, ret, done,
"smb2_session_channel failed");
tree->smbXcli = parent_tree->smbXcli;
tree->session = session;
status = smb2_session_setup_spnego(session,
credentials,
0 /* previous_session_id */);
CHECK_STATUS(status, NT_STATUS_OK);
torture_comment(tctx, "bound new session to parent\n");
}
/*
* We absolutely need to make sure to send something over this
* connection to register the oplock break handler with the smb client
* connection. If we do not send something (at least a keepalive), we
* will *NEVER* receive anything over this transport.
*/
smb2_keepalive(transport);
done:
if (ret) {
return tree;
} else {
return NULL;
}
}
bool test_multichannel_create_channel_array(
struct torture_context *tctx,
const char *host,
const char *share,
struct cli_credentials *credentials,
struct smbcli_options *transport_options,
uint8_t num_trees,
struct smb2_tree **trees)
{
uint8_t i;
transport_options->client_guid = GUID_random();
for (i = 0; i < num_trees; i++) {
struct smb2_tree *parent_tree = NULL;
struct smb2_tree *tree = NULL;
struct smb2_transport *transport = NULL;
uint16_t local_port = 0;
if (i > 0) {
parent_tree = trees[0];
}
torture_comment(tctx, "Setting up connection %d\n", i);
tree = test_multichannel_create_channel(tctx, host, share,
credentials, transport_options,
parent_tree);
torture_assert(tctx, tree, "failed to created new channel");
trees[i] = tree;
transport = tree->session->transport;
local_port = torture_get_local_port_from_transport(transport);
torture_comment(tctx, "transport[%d] uses tcp port: %d\n",
i, local_port);
}
return true;
}
bool test_multichannel_create_channels(
struct torture_context *tctx,
const char *host,
const char *share,
struct cli_credentials *credentials,
struct smbcli_options *transport_options,
struct smb2_tree **tree2A,
struct smb2_tree **tree2B,
struct smb2_tree **tree2C
)
{
struct smb2_tree **trees = NULL;
size_t num_trees = 0;
bool ret;
torture_assert(tctx, tree2A, "tree2A required!");
num_trees += 1;
torture_assert(tctx, tree2B, "tree2B required!");
num_trees += 1;
if (tree2C != NULL) {
num_trees += 1;
}
trees = talloc_zero_array(tctx, struct smb2_tree *, num_trees);
torture_assert(tctx, trees, "out of memory");
ret = test_multichannel_create_channel_array(tctx, host, share, credentials,
transport_options,
num_trees, trees);
if (!ret) {
return false;
}
*tree2A = trees[0];
*tree2B = trees[1];
if (tree2C != NULL) {
*tree2C = trees[2];
}
return true;
}
static void test_multichannel_free_channels(struct smb2_tree *tree2A,
struct smb2_tree *tree2B,
struct smb2_tree *tree2C)
{
TALLOC_FREE(tree2A);
TALLOC_FREE(tree2B);
TALLOC_FREE(tree2C);
}
static bool test_multichannel_initial_checks(struct torture_context *tctx,
struct smb2_tree *tree1)
{
struct smb2_transport *transport1 = tree1->session->transport;
uint32_t server_capabilities;
struct fsctl_net_iface_info info;
if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) {
torture_skip_goto(tctx, fail,
"SMB 3.X Dialect family required for "
"Multichannel tests\n");
}
server_capabilities = smb2cli_conn_server_capabilities(
tree1->session->transport->conn);
if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) {
torture_skip_goto(tctx, fail,
"Server does not support multichannel.");
}
torture_assert(tctx,
test_ioctl_network_interface_info(tctx, tree1, &info),
"failed to retrieve network interface info");
return true;
fail:
return false;
}
static void test_multichannel_init_smb_create(struct smb2_create *io)
{
io->in.durable_open = false;
io->in.durable_open_v2 = true;
io->in.persistent_open = false;
io->in.create_guid = GUID_random();
io->in.timeout = 0x493E0; /* 300000 */
/* windows 2016 returns 300000 0x493E0 */
}
/* 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;
}
/*
* Oplock break - Test 1
* Test to confirm that server sends oplock breaks as expected.
* open file1 in session 2A
* open file2 in session 2B
* open file1 in session 1
* oplock break received
* open file1 in session 1
* oplock break received
* Cleanup
*/
static bool test_multichannel_oplock_break_test1(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_handle _h;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client1_file2 = {{0}};
struct smb2_handle h_client1_file3 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_handle h_client2_file2 = {{0}};
struct smb2_handle h_client2_file3 = {{0}};
struct smb2_create io1, io2, io3;
bool ret = true;
const char *fname1 = BASEDIR "\\oplock_break_test1.dat";
const char *fname2 = BASEDIR "\\oplock_break_test2.dat";
const char *fname3 = BASEDIR "\\oplock_break_test3.dat";
struct smb2_tree *tree2A = NULL;
struct smb2_tree *tree2B = NULL;
struct smb2_tree *tree2C = NULL;
struct smb2_transport *transport1 = tree1->session->transport;
struct smbcli_options transport2_options;
struct smb2_session *session1 = tree1->session;
uint16_t local_port = 0;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Oplock break retry: Test1\n");
torture_reset_break_info(tctx, &break_info);
transport1->oplock.handler = torture_oplock_ack_handler;
transport1->oplock.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
CHECK_VAL(break_info.count, 0);
smb2_oplock_create_share(&io1, fname1,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
test_multichannel_init_smb_create(&io1);
smb2_oplock_create_share(&io2, fname2,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
test_multichannel_init_smb_create(&io2);
smb2_oplock_create_share(&io3, fname3,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
test_multichannel_init_smb_create(&io3);
transport2_options = transport1->options;
ret = test_multichannel_create_channels(tctx, host, share,
credentials,
&transport2_options,
&tree2A, &tree2B, NULL);
torture_assert(tctx, ret, "Could not create channels.\n");
/* 2a opens file1 */
torture_comment(tctx, "client2 opens fname1 via session 2A\n");
status = smb2_create(tree2A, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 0);
/* 2b opens file2 */
torture_comment(tctx, "client2 opens fname2 via session 2B\n");
status = smb2_create(tree2B, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 0);
/* 1 opens file1 - batchoplock break? */
torture_comment(tctx, "client1 opens fname1 via session 1\n");
io1.in.oplock_level = smb2_util_oplock_level("b");
status = smb2_create(tree1, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 1);
torture_reset_break_info(tctx, &break_info);
/* 1 opens file2 - batchoplock break? */
torture_comment(tctx, "client1 opens fname2 via session 1\n");
io2.in.oplock_level = smb2_util_oplock_level("b");
status = smb2_create(tree1, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("s"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 1);
/* cleanup everything */
torture_reset_break_info(tctx, &break_info);
smb2_util_close(tree1, h_client1_file1);
smb2_util_close(tree1, h_client1_file2);
smb2_util_close(tree1, h_client1_file3);
smb2_util_close(tree2A, h_client2_file1);
smb2_util_close(tree2A, h_client2_file2);
smb2_util_close(tree2A, h_client2_file3);
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
CHECK_VAL(break_info.count, 0);
test_multichannel_free_channels(tree2A, tree2B, tree2C);
tree2A = tree2B = tree2C = NULL;
done:
tree1->session = session1;
smb2_util_close(tree1, h_client1_file1);
smb2_util_close(tree1, h_client1_file2);
smb2_util_close(tree1, h_client1_file3);
if (tree2A != NULL) {
smb2_util_close(tree2A, h_client2_file1);
smb2_util_close(tree2A, h_client2_file2);
smb2_util_close(tree2A, h_client2_file3);
}
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
smb2_deltree(tree1, BASEDIR);
test_multichannel_free_channels(tree2A, tree2B, tree2C);
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
/*
* Oplock Break Test 2
* Test to see if oplock break retries are sent by the server.
* Also checks to see if new channels can be created and used
* after an oplock break retry.
* open file1 in 2A
* open file2 in 2B
* open file1 in session 1
* oplock break received
* block channel on which oplock break received
* open file2 in session 1
* oplock break not received. Retry received.
* file opened
* write to file2 on 2B
* Break sent to session 1(which has file2 open)
* Break sent to session 2A(which has read oplock)
* close file1 in session 1
* open file1 with session 1
* unblock blocked channel
* disconnect blocked channel
* connect channel 2D
* open file3 in 2D
* open file3 in session 1
* receive break
*/
static bool test_multichannel_oplock_break_test2(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_handle _h;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client1_file2 = {{0}};
struct smb2_handle h_client1_file3 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_handle h_client2_file2 = {{0}};
struct smb2_handle h_client2_file3 = {{0}};
struct smb2_create io1, io2, io3;
bool ret = true;
const char *fname1 = BASEDIR "\\oplock_break_test1.dat";
const char *fname2 = BASEDIR "\\oplock_break_test2.dat";
const char *fname3 = BASEDIR "\\oplock_break_test3.dat";
struct smb2_tree *tree2A = NULL;
struct smb2_tree *tree2B = NULL;
struct smb2_tree *tree2C = NULL;
struct smb2_tree *tree2D = NULL;
struct smb2_transport *transport1 = tree1->session->transport;
struct smb2_transport *transport2 = NULL;
struct smbcli_options transport2_options;
struct smb2_session *session1 = tree1->session;
uint16_t local_port = 0;
DATA_BLOB blob;
bool block_setup = false;
bool block_ok = false;
bool unblock_ok = false;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Oplock break retry: Test2\n");
torture_reset_break_info(tctx, &break_info);
transport1->oplock.handler = torture_oplock_ack_handler;
transport1->oplock.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
CHECK_VAL(break_info.count, 0);
smb2_oplock_create_share(&io1, fname1,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
test_multichannel_init_smb_create(&io1);
smb2_oplock_create_share(&io2, fname2,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
test_multichannel_init_smb_create(&io2);
smb2_oplock_create_share(&io3, fname3,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
test_multichannel_init_smb_create(&io3);
transport2_options = transport1->options;
ret = test_multichannel_create_channels(tctx, host, share,
credentials,
&transport2_options,
&tree2A, &tree2B, &tree2C);
torture_assert(tctx, ret, "Could not create channels.\n");
torture_comment(tctx, "client2 opens fname1 via session 2A\n");
io1.in.oplock_level = smb2_util_oplock_level("b");
status = smb2_create(tree2A, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 0);
torture_comment(tctx, "client2 opens fname2 via session 2B\n");
io2.in.oplock_level = smb2_util_oplock_level("b");
status = smb2_create(tree2B, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 0);
torture_comment(tctx, "client1 opens fname1 via session 1\n");
io1.in.oplock_level = smb2_util_oplock_level("b");
status = smb2_create(tree1, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 1);
/* We use the transport over which this oplock break was received */
transport2 = break_info.received_transport;
torture_reset_break_info(tctx, &break_info);
block_setup = test_setup_blocked_transports(tctx);
torture_assert(tctx, block_setup, "test_setup_blocked_transports");
/* block channel */
block_ok = test_block_smb2_transport(tctx, transport2);
torture_comment(tctx, "client1 opens fname2 via session 1\n");
io2.in.oplock_level = smb2_util_oplock_level("b");
status = smb2_create(tree1, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("s"));
/*
* Samba downgrades oplock to a level 2 oplock.
* Windows 2016 revokes oplock
*/
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 1);
torture_reset_break_info(tctx, &break_info);
torture_comment(tctx, "Trying write to file2 on tree2B\n");
blob = data_blob_string_const("Here I am");
status = smb2_util_write(tree2B,
h_client2_file2,
blob.data,
0,
blob.length);
torture_assert_ntstatus_ok(tctx, status,
"failed to write file2 via channel 2B");
/*
* Samba: Write triggers 2 oplock breaks
* for session 1 which has file2 open
* for session 2 which has type 2 oplock
* Windows 2016: Only one oplock break for session 1
*/
torture_wait_for_oplock_break(tctx);
CHECK_VAL_GREATER_THAN(break_info.count, 0);
torture_reset_break_info(tctx, &break_info);
torture_comment(tctx, "client1 closes fname2 via session 1\n");
smb2_util_close(tree1, h_client1_file2);
torture_comment(tctx, "client1 opens fname2 via session 1 again\n");
io2.in.oplock_level = smb2_util_oplock_level("b");
status = smb2_create(tree1, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file2 = io2.out.file.handle;
io2.out.alloc_size = 0;
io2.out.size = 0;
CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("s"));
/*
* now add a fourth channel and repeat the test, we need to reestablish
* transport2 because the remote end has invalidated our connection
*/
torture_comment(tctx, "Connecting session 2D\n");
tree2D = test_multichannel_create_channel(tctx, host, share,
credentials, &transport2_options, tree2B);
if (!tree2D) {
goto done;
}
torture_reset_break_info(tctx, &break_info);
torture_comment(tctx, "client 2 opening fname3 over transport2D\n");
status = smb2_create(tree2D, mem_ctx, &io3);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file3 = io3.out.file.handle;
CHECK_CREATED(&io3, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io3.out.oplock_level, smb2_util_oplock_level("b"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 0);
torture_comment(tctx, "client1 opens fname3 via session 1\n");
status = smb2_create(tree1, mem_ctx, &io3);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file3 = io3.out.file.handle;
CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io3.out.oplock_level, smb2_util_oplock_level("s"));
torture_wait_for_oplock_break(tctx);
CHECK_VAL(break_info.count, 1);
done:
if (block_ok && !unblock_ok) {
test_unblock_smb2_transport(tctx, transport2);
}
test_cleanup_blocked_transports(tctx);
tree1->session = session1;
smb2_util_close(tree1, h_client1_file1);
smb2_util_close(tree1, h_client1_file2);
smb2_util_close(tree1, h_client1_file3);
if (tree2B != NULL) {
smb2_util_close(tree2B, h_client2_file1);
smb2_util_close(tree2B, h_client2_file2);
smb2_util_close(tree2B, h_client2_file3);
}
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
smb2_deltree(tree1, BASEDIR);
test_multichannel_free_channels(tree2A, tree2B, tree2C);
if (tree2D != NULL) {
TALLOC_FREE(tree2D);
}
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
struct test_multichannel_oplock_break_state;
struct test_multichannel_oplock_break_channel {
struct test_multichannel_oplock_break_state *state;
size_t idx;
char name[64];
struct smb2_tree *tree;
bool blocked;
struct timeval break_time;
double full_duration;
double relative_duration;
uint8_t level;
size_t break_num;
};
struct test_multichannel_oplock_break_state {
struct torture_context *tctx;
struct timeval open_req_time;
struct timeval open_rep_time;
size_t num_breaks;
struct timeval last_break_time;
struct test_multichannel_oplock_break_channel channels[32];
};
static bool test_multichannel_oplock_break_handler(struct smb2_transport *transport,
const struct smb2_handle *handle,
uint8_t level,
void *private_data)
{
struct test_multichannel_oplock_break_channel *c =
(struct test_multichannel_oplock_break_channel *)private_data;
struct test_multichannel_oplock_break_state *state = c->state;
c->break_time = timeval_current();
c->full_duration = timeval_elapsed2(&state->open_req_time,
&c->break_time);
c->relative_duration = timeval_elapsed2(&state->last_break_time,
&c->break_time);
state->last_break_time = c->break_time;
c->level = level;
c->break_num = ++state->num_breaks;
torture_comment(state->tctx, "Got OPLOCK break %zu on %s after %f ( %f)\n",
c->break_num, c->name,
c->relative_duration,
c->full_duration);
return torture_oplock_ack_handler(transport, handle, level, c->tree);
}
static bool test_multichannel_oplock_break_test3_windows(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct test_multichannel_oplock_break_state state = {
.tctx = tctx,
};
struct test_multichannel_oplock_break_channel *open2_channel = NULL;
struct smb2_handle _h;
struct smb2_handle *h = NULL;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_create io1;
struct smb2_create io2;
bool ret = true;
const char *fname1 = BASEDIR "\\oplock_break_test3w.dat";
struct smb2_tree *trees2[32] = { NULL, };
size_t i;
struct smb2_transport *transport1 = tree1->session->transport;
struct smbcli_options transport2_options;
struct smb2_session *session1 = tree1->session;
uint16_t local_port = 0;
bool block_setup = false;
bool block_ok = false;
double open_duration;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Oplock break retry: Test3 (Windows behavior)\n");
torture_reset_break_info(tctx, &break_info);
break_info.oplock_skip_ack = true;
transport1->oplock.handler = torture_oplock_ack_handler;
transport1->oplock.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
CHECK_VAL(break_info.count, 0);
smb2_oplock_create_share(&io2, fname1,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
transport2_options = transport1->options;
ret = test_multichannel_create_channel_array(tctx, host, share, credentials,
&transport2_options,
ARRAY_SIZE(trees2), trees2);
torture_assert(tctx, ret, "Could not create channels.\n");
for (i = 0; i < ARRAY_SIZE(trees2); i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
struct smb2_transport *t = trees2[i]->session->transport;
c->state = &state;
c->idx = i+1;
c->tree = trees2[i];
snprintf(c->name, sizeof(c->name), "trees2_%zu", c->idx);
t->oplock.handler = test_multichannel_oplock_break_handler;
t->oplock.private_data = c;
}
open2_channel = &state.channels[0];
/* 2a opens file1 */
torture_comment(tctx, "client2 opens fname1 via %s\n",
open2_channel->name);
status = smb2_create(open2_channel->tree, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io2.out.file.handle;
CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
CHECK_VAL(io2.out.durable_open_v2, false);
CHECK_VAL(io2.out.timeout, io2.in.timeout);
CHECK_VAL(io2.out.durable_open, false);
CHECK_VAL(break_info.count, 0);
block_setup = test_setup_blocked_transports(tctx);
torture_assert(tctx, block_setup, "test_setup_blocked_transports");
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
struct smb2_transport *t = c->tree->session->transport;
torture_comment(tctx, "Blocking %s\n", c->name);
block_ok = _test_block_smb2_transport(tctx, t, c->name);
torture_assert_goto(tctx, block_ok, ret, done, "we could not block tcp transport");
c->blocked = true;
}
/* 1 opens file2 */
torture_comment(tctx,
"Client opens fname1 with session 1 with all %zu blocked\n",
ARRAY_SIZE(trees2));
smb2_oplock_create_share(&io1, fname1,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
CHECK_VAL(lease_break_info.count, 0);
state.open_req_time = timeval_current();
state.last_break_time = state.open_req_time;
status = smb2_create(tree1, mem_ctx, &io1);
state.open_rep_time = timeval_current();
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s"));
CHECK_VAL(break_info.count, 1);
open_duration = timeval_elapsed2(&state.open_req_time,
&state.open_rep_time);
torture_comment(tctx, "open_duration: %f\n", open_duration);
CHECK_VAL_GREATER_THAN(open_duration, 35);
if (break_info.count == 0) {
torture_comment(tctx,
"Did not receive expected oplock break!!\n");
} else {
torture_comment(tctx, "Received %d oplock break(s)!!\n",
break_info.count);
}
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
size_t expected_break_num = 0;
/*
* Only the latest channel gets a break notification
*/
if (i == (ARRAY_SIZE(state.channels) - 1)) {
expected_break_num = 1;
}
torture_comment(tctx, "Verify %s\n", c->name);
torture_assert_int_equal(tctx, c->break_num, expected_break_num,
"Got oplock break on wrong channel");
if (expected_break_num != 0) {
CHECK_VAL(c->level, smb2_util_oplock_level("s"));
}
}
done:
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
struct smb2_transport *t = NULL;
if (!c->blocked) {
continue;
}
t = c->tree->session->transport;
torture_comment(tctx, "Unblocking %s\n", c->name);
_test_unblock_smb2_transport(tctx, t, c->name);
c->blocked = false;
}
if (block_setup) {
test_cleanup_blocked_transports(tctx);
}
tree1->session = session1;
smb2_util_close(tree1, h_client1_file1);
if (trees2[0] != NULL) {
smb2_util_close(trees2[0], h_client2_file1);
}
if (h != NULL) {
smb2_util_close(tree1, *h);
}
smb2_util_unlink(tree1, fname1);
smb2_deltree(tree1, BASEDIR);
for (i = 0; i < ARRAY_SIZE(trees2); i++) {
if (trees2[i] == NULL) {
continue;
}
TALLOC_FREE(trees2[i]);
}
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
static bool test_multichannel_oplock_break_test3_specification(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct test_multichannel_oplock_break_state state = {
.tctx = tctx,
};
struct test_multichannel_oplock_break_channel *open2_channel = NULL;
struct smb2_handle _h;
struct smb2_handle *h = NULL;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_create io1;
struct smb2_create io2;
bool ret = true;
const char *fname1 = BASEDIR "\\oplock_break_test3s.dat";
struct smb2_tree *trees2[32] = { NULL, };
size_t i;
struct smb2_transport *transport1 = tree1->session->transport;
struct smbcli_options transport2_options;
struct smb2_session *session1 = tree1->session;
uint16_t local_port = 0;
bool block_setup = false;
bool block_ok = false;
double open_duration;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Oplock break retry: Test3 (Specification behavior)\n");
torture_reset_break_info(tctx, &break_info);
break_info.oplock_skip_ack = true;
transport1->oplock.handler = torture_oplock_ack_handler;
transport1->oplock.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
CHECK_VAL(break_info.count, 0);
smb2_oplock_create_share(&io2, fname1,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
transport2_options = transport1->options;
ret = test_multichannel_create_channel_array(tctx, host, share, credentials,
&transport2_options,
ARRAY_SIZE(trees2), trees2);
torture_assert(tctx, ret, "Could not create channels.\n");
for (i = 0; i < ARRAY_SIZE(trees2); i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
struct smb2_transport *t = trees2[i]->session->transport;
c->state = &state;
c->idx = i+1;
c->tree = trees2[i];
snprintf(c->name, sizeof(c->name), "trees2_%zu", c->idx);
t->oplock.handler = test_multichannel_oplock_break_handler;
t->oplock.private_data = c;
}
open2_channel = &state.channels[0];
/* 2a opens file1 */
torture_comment(tctx, "client2 opens fname1 via %s\n",
open2_channel->name);
status = smb2_create(open2_channel->tree, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io2.out.file.handle;
CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_VAL(io2.out.oplock_level, smb2_util_oplock_level("b"));
CHECK_VAL(io2.out.durable_open_v2, false);
CHECK_VAL(io2.out.timeout, io2.in.timeout);
CHECK_VAL(io2.out.durable_open, false);
CHECK_VAL(break_info.count, 0);
block_setup = test_setup_blocked_transports(tctx);
torture_assert(tctx, block_setup, "test_setup_blocked_transports");
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
struct smb2_transport *t = c->tree->session->transport;
torture_comment(tctx, "Blocking %s\n", c->name);
block_ok = _test_block_smb2_transport(tctx, t, c->name);
torture_assert_goto(tctx, block_ok, ret, done, "we could not block tcp transport");
c->blocked = true;
}
/* 1 opens file2 */
torture_comment(tctx,
"Client opens fname1 with session 1 with all %zu blocked\n",
ARRAY_SIZE(trees2));
smb2_oplock_create_share(&io1, fname1,
smb2_util_share_access("RWD"),
smb2_util_oplock_level("b"));
CHECK_VAL(lease_break_info.count, 0);
state.open_req_time = timeval_current();
state.last_break_time = state.open_req_time;
status = smb2_create(tree1, mem_ctx, &io1);
state.open_rep_time = timeval_current();
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_VAL_GREATER_THAN(break_info.count, 1);
open_duration = timeval_elapsed2(&state.open_req_time,
&state.open_rep_time);
torture_comment(tctx, "open_duration: %f\n", open_duration);
if (break_info.count < ARRAY_SIZE(state.channels)) {
CHECK_VAL_GREATER_THAN(open_duration, 35);
CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("s"));
} else {
CHECK_VAL(io1.out.oplock_level, smb2_util_oplock_level("b"));
}
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
if (break_info.count >= ARRAY_SIZE(state.channels)) {
break;
}
torture_comment(tctx, "Received %d oplock break(s) wait for more!!\n",
break_info.count);
torture_wait_for_oplock_break(tctx);
}
if (break_info.count == 0) {
torture_comment(tctx,
"Did not receive expected oplock break!!\n");
} else {
torture_comment(tctx, "Received %d oplock break(s)!!\n",
break_info.count);
}
if (break_info.count < ARRAY_SIZE(state.channels)) {
CHECK_VAL_GREATER_THAN(break_info.count, 3);
} else {
CHECK_VAL(break_info.count, ARRAY_SIZE(state.channels));
}
for (i = 0; i < break_info.count; i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
torture_comment(tctx, "Verify %s\n", c->name);
torture_assert_int_equal(tctx, c->break_num, c->idx,
"Got oplock break on wrong channel");
CHECK_VAL(c->level, smb2_util_oplock_level("s"));
}
done:
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
struct test_multichannel_oplock_break_channel *c = &state.channels[i];
struct smb2_transport *t = NULL;
if (!c->blocked) {
continue;
}
t = c->tree->session->transport;
torture_comment(tctx, "Unblocking %s\n", c->name);
_test_unblock_smb2_transport(tctx, t, c->name);
c->blocked = false;
}
if (block_setup) {
test_cleanup_blocked_transports(tctx);
}
tree1->session = session1;
smb2_util_close(tree1, h_client1_file1);
if (trees2[0] != NULL) {
smb2_util_close(trees2[0], h_client2_file1);
}
if (h != NULL) {
smb2_util_close(tree1, *h);
}
smb2_util_unlink(tree1, fname1);
smb2_deltree(tree1, BASEDIR);
for (i = 0; i < ARRAY_SIZE(trees2); i++) {
if (trees2[i] == NULL) {
continue;
}
TALLOC_FREE(trees2[i]);
}
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
static const uint64_t LEASE1F1 = 0xBADC0FFEE0DDF00Dull;
static const uint64_t LEASE1F2 = 0xBADC0FFEE0DDD00Dull;
static const uint64_t LEASE1F3 = 0xDADC0FFEE0DDD00Dull;
static const uint64_t LEASE2F1 = 0xDEADBEEFFEEDBEADull;
static const uint64_t LEASE2F2 = 0xDAD0FFEDD00DF00Dull;
static const uint64_t LEASE2F3 = 0xBAD0FFEDD00DF00Dull;
/*
* Lease Break Test 1:
* Test to check if lease breaks are sent by the server as expected.
* open file1 in session 2A
* open file2 in session 2B
* open file3 in session 2C
* open file1 in session 1
* lease break sent
* open file2 in session 1
* lease break sent
* open file3 in session 1
* lease break sent
*/
static bool test_multichannel_lease_break_test1(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_handle _h;
struct smb2_handle *h = NULL;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client1_file2 = {{0}};
struct smb2_handle h_client1_file3 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_handle h_client2_file2 = {{0}};
struct smb2_handle h_client2_file3 = {{0}};
struct smb2_create io1, io2, io3;
bool ret = true;
const char *fname1 = BASEDIR "\\lease_break_test1.dat";
const char *fname2 = BASEDIR "\\lease_break_test2.dat";
const char *fname3 = BASEDIR "\\lease_break_test3.dat";
struct smb2_tree *tree2A = NULL;
struct smb2_tree *tree2B = NULL;
struct smb2_tree *tree2C = NULL;
struct smb2_transport *transport1 = tree1->session->transport;
struct smbcli_options transport2_options;
struct smb2_session *session1 = tree1->session;
uint16_t local_port = 0;
struct smb2_lease ls1;
struct smb2_lease ls2;
struct smb2_lease ls3;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Lease break retry: Test1\n");
torture_reset_lease_break_info(tctx, &lease_break_info);
transport1->lease.handler = torture_lease_handler;
transport1->lease.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
CHECK_VAL(lease_break_info.count, 0);
smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1,
smb2_util_lease_state("RHW"));
test_multichannel_init_smb_create(&io1);
smb2_lease_create(&io2, &ls2, false, fname2, LEASE2F2,
smb2_util_lease_state("RHW"));
test_multichannel_init_smb_create(&io2);
smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3,
smb2_util_lease_state("RHW"));
test_multichannel_init_smb_create(&io3);
transport2_options = transport1->options;
ret = test_multichannel_create_channels(tctx, host, share,
credentials,
&transport2_options,
&tree2A, &tree2B, &tree2C);
torture_assert(tctx, ret, "Could not create channels.\n");
/* 2a opens file1 */
torture_comment(tctx, "client2 opens fname1 via session 2A\n");
status = smb2_create(tree2A, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io1, "RHW", true, LEASE2F1, 0);
CHECK_VAL(lease_break_info.count, 0);
/* 2b opens file2 */
torture_comment(tctx, "client2 opens fname2 via session 2B\n");
status = smb2_create(tree2B, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io2, "RHW", true, LEASE2F2, 0);
CHECK_VAL(lease_break_info.count, 0);
/* 2c opens file3 */
torture_comment(tctx, "client2 opens fname3 via session 2C\n");
smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3,
smb2_util_lease_state("RHW"));
status = smb2_create(tree2C, mem_ctx, &io3);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file3 = io3.out.file.handle;
CHECK_CREATED(&io3, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io3, "RHW", true, LEASE2F3, 0);
CHECK_VAL(lease_break_info.count, 0);
/* 1 opens file1 - lease break? */
torture_comment(tctx, "client1 opens fname1 via session 1\n");
smb2_lease_create(&io1, &ls1, false, fname1, LEASE1F1,
smb2_util_lease_state("RHW"));
status = smb2_create(tree1, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io1, "RH", true, LEASE1F1, 0);
CHECK_BREAK_INFO("RHW", "RH", LEASE2F1);
CHECK_VAL(lease_break_info.count, 1);
torture_reset_lease_break_info(tctx, &lease_break_info);
/* 1 opens file2 - lease break? */
torture_comment(tctx, "client1 opens fname2 via session 1\n");
smb2_lease_create(&io2, &ls2, false, fname2, LEASE1F2,
smb2_util_lease_state("RHW"));
status = smb2_create(tree1, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io2, "RH", true, LEASE1F2, 0);
CHECK_BREAK_INFO("RHW", "RH", LEASE2F2);
CHECK_VAL(lease_break_info.count, 1);
torture_reset_lease_break_info(tctx, &lease_break_info);
/* 1 opens file3 - lease break? */
torture_comment(tctx, "client1 opens fname3 via session 1\n");
smb2_lease_create(&io3, &ls3, false, fname3, LEASE1F3,
smb2_util_lease_state("RHW"));
status = smb2_create(tree1, mem_ctx, &io3);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file3 = io3.out.file.handle;
CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io3, "RH", true, LEASE1F3, 0);
CHECK_BREAK_INFO("RHW", "RH", LEASE2F3);
CHECK_VAL(lease_break_info.count, 1);
/* cleanup everything */
torture_reset_lease_break_info(tctx, &lease_break_info);
smb2_util_close(tree1, h_client1_file1);
smb2_util_close(tree1, h_client1_file2);
smb2_util_close(tree1, h_client1_file3);
smb2_util_close(tree2A, h_client2_file1);
smb2_util_close(tree2A, h_client2_file2);
smb2_util_close(tree2A, h_client2_file3);
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
CHECK_VAL(lease_break_info.count, 0);
test_multichannel_free_channels(tree2A, tree2B, tree2C);
tree2A = tree2B = tree2C = NULL;
done:
tree1->session = session1;
smb2_util_close(tree1, h_client1_file1);
smb2_util_close(tree1, h_client1_file2);
smb2_util_close(tree1, h_client1_file3);
if (tree2A != NULL) {
smb2_util_close(tree2A, h_client2_file1);
smb2_util_close(tree2A, h_client2_file2);
smb2_util_close(tree2A, h_client2_file3);
}
if (h != NULL) {
smb2_util_close(tree1, *h);
}
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
smb2_deltree(tree1, BASEDIR);
test_multichannel_free_channels(tree2A, tree2B, tree2C);
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
/*
* Lease Break Test 2:
* Test for lease break retries being sent by the server.
* Connect 2A, 2B
* open file1 in session 2A
* open file2 in session 2B
* block 2A
* open file2 in session 1
* lease break retry reaches the client?
* Connect 2C
* open file3 in session 2C
* unblock 2A
* open file1 in session 1
* lease break reaches the client?
* open file3 in session 1
* lease break reached the client?
* Cleanup
* On deletion by 1, lease breaks sent for file1, file2 and file3
* on 2B
* This changes RH lease to R for Session 2.
* (This has been disabled while we add support for sending lease
* break for handle leases.)
*/
static bool test_multichannel_lease_break_test2(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_handle _h;
struct smb2_handle *h = NULL;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client1_file2 = {{0}};
struct smb2_handle h_client1_file3 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_handle h_client2_file2 = {{0}};
struct smb2_handle h_client2_file3 = {{0}};
struct smb2_create io1, io2, io3;
bool ret = true;
const char *fname1 = BASEDIR "\\lease_break_test1.dat";
const char *fname2 = BASEDIR "\\lease_break_test2.dat";
const char *fname3 = BASEDIR "\\lease_break_test3.dat";
struct smb2_tree *tree2A = NULL;
struct smb2_tree *tree2B = NULL;
struct smb2_tree *tree2C = NULL;
struct smb2_transport *transport1 = tree1->session->transport;
struct smb2_transport *transport2A = NULL;
struct smbcli_options transport2_options;
struct smb2_session *session1 = tree1->session;
uint16_t local_port = 0;
struct smb2_lease ls1;
struct smb2_lease ls2;
struct smb2_lease ls3;
bool block_setup = false;
bool block_ok = false;
bool unblock_ok = false;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Lease break retry: Test2\n");
torture_reset_lease_break_info(tctx, &lease_break_info);
transport1->lease.handler = torture_lease_handler;
transport1->lease.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
CHECK_VAL(lease_break_info.count, 0);
smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1,
smb2_util_lease_state("RHW"));
test_multichannel_init_smb_create(&io1);
smb2_lease_create(&io2, &ls2, false, fname2, LEASE2F2,
smb2_util_lease_state("RHW"));
test_multichannel_init_smb_create(&io2);
smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3,
smb2_util_lease_state("RHW"));
test_multichannel_init_smb_create(&io3);
transport2_options = transport1->options;
ret = test_multichannel_create_channels(tctx, host, share,
credentials,
&transport2_options,
&tree2A, &tree2B, NULL);
torture_assert(tctx, ret, "Could not create channels.\n");
transport2A = tree2A->session->transport;
/* 2a opens file1 */
torture_comment(tctx, "client2 opens fname1 via session 2A\n");
smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1,
smb2_util_lease_state("RHW"));
status = smb2_create(tree2A, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io1, "RHW", true, LEASE2F1, 0);
CHECK_VAL(io1.out.durable_open_v2, false); //true);
CHECK_VAL(io1.out.timeout, io1.in.timeout);
CHECK_VAL(io1.out.durable_open, false);
CHECK_VAL(lease_break_info.count, 0);
/* 2b opens file2 */
torture_comment(tctx, "client2 opens fname2 via session 2B\n");
smb2_lease_create(&io2, &ls2, false, fname2, LEASE2F2,
smb2_util_lease_state("RHW"));
status = smb2_create(tree2B, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io2, "RHW", true, LEASE2F2, 0);
CHECK_VAL(io2.out.durable_open_v2, false); //true);
CHECK_VAL(io2.out.timeout, io2.in.timeout);
CHECK_VAL(io2.out.durable_open, false);
CHECK_VAL(lease_break_info.count, 0);
block_setup = test_setup_blocked_transports(tctx);
torture_assert(tctx, block_setup, "test_setup_blocked_transports");
torture_comment(tctx, "Blocking 2A\n");
/* Block 2A */
block_ok = test_block_smb2_transport(tctx, transport2A);
torture_assert(tctx, block_ok, "we could not block tcp transport");
torture_wait_for_lease_break(tctx);
CHECK_VAL(lease_break_info.count, 0);
/* 1 opens file2 */
torture_comment(tctx,
"Client opens fname2 with session1 with 2A blocked\n");
smb2_lease_create(&io2, &ls2, false, fname2, LEASE1F2,
smb2_util_lease_state("RHW"));
status = smb2_create(tree1, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file2 = io2.out.file.handle;
CHECK_CREATED(&io2, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io2, "RH", true, LEASE1F2, 0);
CHECK_VAL(io2.out.durable_open_v2, false);
CHECK_VAL(io2.out.timeout, 0);
CHECK_VAL(io2.out.durable_open, false);
if (lease_break_info.count == 0) {
torture_comment(tctx,
"Did not receive expected lease break!!\n");
} else {
torture_comment(tctx, "Received %d lease break(s)!!\n",
lease_break_info.count);
}
/*
* We got breaks on both channels
* (one failed on the blocked connection)
*/
CHECK_VAL(lease_break_info.count, 2);
lease_break_info.count -= 1;
CHECK_VAL(lease_break_info.failures, 1);
lease_break_info.failures -= 1;
CHECK_BREAK_INFO("RHW", "RH", LEASE2F2);
torture_reset_lease_break_info(tctx, &lease_break_info);
/* Connect 2C */
torture_comment(tctx, "Connecting session 2C\n");
talloc_free(tree2C);
tree2C = test_multichannel_create_channel(tctx, host, share,
credentials, &transport2_options, tree2A);
if (!tree2C) {
goto done;
}
/* 2c opens file3 */
torture_comment(tctx, "client2 opens fname3 via session 2C\n");
smb2_lease_create(&io3, &ls3, false, fname3, LEASE2F3,
smb2_util_lease_state("RHW"));
status = smb2_create(tree2C, mem_ctx, &io3);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file3 = io3.out.file.handle;
CHECK_CREATED(&io3, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io3, "RHW", true, LEASE2F3, 0);
CHECK_VAL(io3.out.durable_open_v2, false);
CHECK_VAL(io3.out.timeout, io2.in.timeout);
CHECK_VAL(io3.out.durable_open, false);
CHECK_VAL(lease_break_info.count, 0);
/* Unblock 2A */
torture_comment(tctx, "Unblocking 2A\n");
unblock_ok = test_unblock_smb2_transport(tctx, transport2A);
torture_assert(tctx, unblock_ok, "we could not unblock tcp transport");
/* 1 opens file1 */
torture_comment(tctx, "Client opens fname1 with session 1\n");
smb2_lease_create(&io1, &ls1, false, fname1, LEASE1F1,
smb2_util_lease_state("RHW"));
status = smb2_create(tree1, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io1, "RH", true, LEASE1F1, 0);
if (lease_break_info.count == 0) {
torture_comment(tctx,
"Did not receive expected lease break!!\n");
} else {
torture_comment(tctx,
"Received %d lease break(s)!!\n",
lease_break_info.count);
}
CHECK_VAL(lease_break_info.count, 1);
CHECK_BREAK_INFO("RHW", "RH", LEASE2F1);
torture_reset_lease_break_info(tctx, &lease_break_info);
/*1 opens file3 */
torture_comment(tctx, "client opens fname3 via session 1\n");
smb2_lease_create(&io3, &ls3, false, fname3, LEASE1F3,
smb2_util_lease_state("RHW"));
status = smb2_create(tree1, mem_ctx, &io3);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file3 = io3.out.file.handle;
CHECK_CREATED(&io3, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io3, "RH", true, LEASE1F3, 0);
if (lease_break_info.count == 0) {
torture_comment(tctx,
"Did not receive expected lease break!!\n");
} else {
torture_comment(tctx,
"Received %d lease break(s)!!\n",
lease_break_info.count);
}
CHECK_VAL(lease_break_info.count, 1);
CHECK_BREAK_INFO("RHW", "RH", LEASE2F3);
torture_reset_lease_break_info(tctx, &lease_break_info);
smb2_util_close(tree1, h_client1_file1);
smb2_util_close(tree1, h_client1_file2);
smb2_util_close(tree1, h_client1_file3);
/*
* Session 2 still has RW lease on file 1. Deletion of this file by 1
* leads to a lease break call to session 2 file1
*/
smb2_util_unlink(tree1, fname1);
/*
* Bug - Samba does not revoke Handle lease on unlink
* CHECK_BREAK_INFO("RH", "R", LEASE2F1);
*/
torture_reset_lease_break_info(tctx, &lease_break_info);
/*
* Session 2 still has RW lease on file 2. Deletion of this file by 1
* leads to a lease break call to session 2 file2
*/
smb2_util_unlink(tree1, fname2);
/*
* Bug - Samba does not revoke Handle lease on unlink
* CHECK_BREAK_INFO("RH", "R", LEASE2F2);
*/
torture_reset_lease_break_info(tctx, &lease_break_info);
/*
* Session 2 still has RW lease on file 3. Deletion of this file by 1
* leads to a lease break call to session 2 file3
*/
smb2_util_unlink(tree1, fname3);
/*
* Bug - Samba does not revoke Handle lease on unlink
* CHECK_BREAK_INFO("RH", "R", LEASE2F3);
*/
torture_reset_lease_break_info(tctx, &lease_break_info);
smb2_util_close(tree2C, h_client2_file1);
smb2_util_close(tree2C, h_client2_file2);
smb2_util_close(tree2C, h_client2_file3);
test_multichannel_free_channels(tree2A, tree2B, tree2C);
tree2A = tree2B = tree2C = NULL;
done:
if (block_ok && !unblock_ok) {
test_unblock_smb2_transport(tctx, transport2A);
}
if (block_setup) {
test_cleanup_blocked_transports(tctx);
}
tree1->session = session1;
smb2_util_close(tree1, h_client1_file1);
smb2_util_close(tree1, h_client1_file2);
smb2_util_close(tree1, h_client1_file3);
if (tree2A != NULL) {
smb2_util_close(tree2A, h_client2_file1);
smb2_util_close(tree2A, h_client2_file2);
smb2_util_close(tree2A, h_client2_file3);
}
if (h != NULL) {
smb2_util_close(tree1, *h);
}
smb2_util_unlink(tree1, fname1);
smb2_util_unlink(tree1, fname2);
smb2_util_unlink(tree1, fname3);
smb2_deltree(tree1, BASEDIR);
test_multichannel_free_channels(tree2A, tree2B, tree2C);
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
/*
* Test 3: Check to see how the server behaves if lease break
* response is sent over a different channel to one over which
* the break is received.
* Connect 2A, 2B
* open file1 in session 2A
* open file1 in session 1
* Lease break sent to 2A
* 2B sends back lease break reply.
* session 1 allowed to open file
*/
static bool test_multichannel_lease_break_test3(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct smb2_handle _h;
struct smb2_handle *h = NULL;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_create io1;
bool ret = true;
const char *fname1 = BASEDIR "\\lease_break_test1.dat";
struct smb2_tree *tree2A = NULL;
struct smb2_tree *tree2B = NULL;
struct smb2_transport *transport1 = tree1->session->transport;
struct smb2_transport *transport2A = NULL;
struct smbcli_options transport2_options;
uint16_t local_port = 0;
struct smb2_lease ls1;
struct tevent_timer *te = NULL;
struct timeval ne;
bool timesup = false;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Lease break retry: Test3\n");
torture_reset_lease_break_info(tctx, &lease_break_info);
transport1->lease.handler = torture_lease_handler;
transport1->lease.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
CHECK_VAL(lease_break_info.count, 0);
smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1,
smb2_util_lease_state("RHW"));
test_multichannel_init_smb_create(&io1);
transport2_options = transport1->options;
ret = test_multichannel_create_channels(tctx, host, share,
credentials,
&transport2_options,
&tree2A, &tree2B, NULL);
torture_assert(tctx, ret, "Could not create channels.\n");
transport2A = tree2A->session->transport;
transport2A->lease.private_data = tree2B;
/* 2a opens file1 */
torture_comment(tctx, "client2 opens fname1 via session 2A\n");
smb2_lease_create(&io1, &ls1, false, fname1, LEASE2F1,
smb2_util_lease_state("RHW"));
status = smb2_create(tree2A, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io1, "RHW", true, LEASE2F1, 0);
CHECK_VAL(io1.out.durable_open_v2, false); //true);
CHECK_VAL(io1.out.timeout, io1.in.timeout);
CHECK_VAL(io1.out.durable_open, false);
CHECK_VAL(lease_break_info.count, 0);
/* Set a timeout for 5 seconds for session 1 to open file1 */
ne = tevent_timeval_current_ofs(0, 5000000);
te = tevent_add_timer(tctx->ev, mem_ctx, ne, timeout_cb, &timesup);
if (te == NULL) {
torture_comment(tctx, "Failed to add timer.");
goto done;
}
/* 1 opens file2 */
torture_comment(tctx, "Client opens fname1 with session 1\n");
smb2_lease_create(&io1, &ls1, false, fname1, LEASE1F1,
smb2_util_lease_state("RHW"));
status = smb2_create(tree1, mem_ctx, &io1);
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_CREATED(&io1, EXISTED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE(&io1, "RH", true, LEASE1F1, 0);
CHECK_VAL(io1.out.durable_open_v2, false);
CHECK_VAL(io1.out.timeout, 0);
CHECK_VAL(io1.out.durable_open, false);
CHECK_VAL(lease_break_info.count, 1);
CHECK_BREAK_INFO("RHW", "RH", LEASE2F1);
/*
* Check if timeout handler was fired. This would indicate
* that the server didn't receive a reply for the oplock break
* from the client and the server let session 1 open the file
* only after the oplock break timeout.
*/
CHECK_VAL(timesup, false);
done:
smb2_util_close(tree1, h_client1_file1);
if (tree2A != NULL) {
smb2_util_close(tree2A, h_client2_file1);
}
if (h != NULL) {
smb2_util_close(tree1, *h);
}
smb2_util_unlink(tree1, fname1);
smb2_deltree(tree1, BASEDIR);
test_multichannel_free_channels(tree2A, tree2B, NULL);
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
/*
* Test limits of channels
*/
static bool test_multichannel_num_channels(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 = samba_cmdline_get_creds();
TALLOC_CTX *mem_ctx = talloc_new(tctx);
bool ret = true;
struct smb2_tree **tree2 = NULL;
struct smb2_transport *transport1 = tree1->session->transport;
struct smb2_transport **transport2 = NULL;
struct smbcli_options transport2_options;
struct smb2_session **session2 = NULL;
uint32_t server_capabilities;
int i;
int max_channels = 33; /* 32 is the W2K12R2 and W2K16 limit */
if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) {
torture_fail(tctx,
"SMB 3.X Dialect family required for Multichannel"
" tests\n");
}
server_capabilities = smb2cli_conn_server_capabilities(
tree1->session->transport->conn);
if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) {
torture_fail(tctx,
"Server does not support multichannel.");
}
torture_comment(tctx, "Testing max. number of channels\n");
transport2_options = transport1->options;
transport2_options.client_guid = GUID_random();
tree2 = talloc_zero_array(mem_ctx, struct smb2_tree *,
max_channels);
transport2 = talloc_zero_array(mem_ctx, struct smb2_transport *,
max_channels);
session2 = talloc_zero_array(mem_ctx, struct smb2_session *,
max_channels);
if (tree2 == NULL || transport2 == NULL || session2 == NULL) {
torture_fail(tctx, "out of memory");
}
for (i = 0; i < max_channels; i++) {
NTSTATUS expected_status;
torture_assert_ntstatus_ok_goto(tctx,
smb2_connect(tctx,
host,
lpcfg_smb_ports(tctx->lp_ctx),
share,
lpcfg_resolve_context(tctx->lp_ctx),
credentials,
&tree2[i],
tctx->ev,
&transport2_options,
lpcfg_socket_options(tctx->lp_ctx),
lpcfg_gensec_settings(tctx, tctx->lp_ctx)
),
ret, done, "smb2_connect failed");
transport2[i] = tree2[i]->session->transport;
if (i == 0) {
/*
* done for the 1st channel
*
* For all remaining channels we do the
* session setup on our own.
*/
transport2_options.only_negprot = true;
continue;
}
/*
* Now bind the session2[i] to the transport2
*/
session2[i] = smb2_session_channel(transport2[i],
lpcfg_gensec_settings(tctx,
tctx->lp_ctx),
tree2[0],
tree2[0]->session);
torture_assert(tctx, session2[i] != NULL,
"smb2_session_channel failed");
torture_comment(tctx, "established transport2 [#%d]\n", i);
if (i >= 32) {
expected_status = NT_STATUS_INSUFFICIENT_RESOURCES;
} else {
expected_status = NT_STATUS_OK;
}
torture_assert_ntstatus_equal_goto(tctx,
smb2_session_setup_spnego(session2[i],
samba_cmdline_get_creds(),
0 /* previous_session_id */),
expected_status,
ret, done,
talloc_asprintf(tctx, "failed to establish session "
"setup for channel #%d", i));
torture_comment(tctx, "bound session2 [#%d] to session2 [0]\n",
i);
}
done:
talloc_free(mem_ctx);
return ret;
}
struct test_multichannel_lease_break_state;
struct test_multichannel_lease_break_channel {
struct test_multichannel_lease_break_state *state;
size_t idx;
char name[64];
struct smb2_tree *tree;
bool blocked;
struct timeval break_time;
double full_duration;
double relative_duration;
struct smb2_lease_break lb;
size_t break_num;
};
struct test_multichannel_lease_break_state {
struct torture_context *tctx;
struct timeval open_req_time;
struct timeval open_rep_time;
size_t num_breaks;
struct timeval last_break_time;
struct test_multichannel_lease_break_channel channels[32];
};
static bool test_multichannel_lease_break_handler(struct smb2_transport *transport,
const struct smb2_lease_break *lb,
void *private_data)
{
struct test_multichannel_lease_break_channel *c =
(struct test_multichannel_lease_break_channel *)private_data;
struct test_multichannel_lease_break_state *state = c->state;
c->break_time = timeval_current();
c->full_duration = timeval_elapsed2(&state->open_req_time,
&c->break_time);
c->relative_duration = timeval_elapsed2(&state->last_break_time,
&c->break_time);
state->last_break_time = c->break_time;
c->lb = *lb;
c->break_num = ++state->num_breaks;
torture_comment(state->tctx, "Got LEASE break epoch[0x%x] %zu on %s after %f ( %f)\n",
c->lb.new_epoch, c->break_num, c->name,
c->relative_duration,
c->full_duration);
return torture_lease_handler(transport, lb, c->tree);
}
static bool test_multichannel_lease_break_test4(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 = samba_cmdline_get_creds();
NTSTATUS status;
TALLOC_CTX *mem_ctx = talloc_new(tctx);
struct test_multichannel_lease_break_state state = {
.tctx = tctx,
};
struct test_multichannel_lease_break_channel *open2_channel = NULL;
struct smb2_handle _h;
struct smb2_handle *h = NULL;
struct smb2_handle h_client1_file1 = {{0}};
struct smb2_handle h_client2_file1 = {{0}};
struct smb2_create io1;
struct smb2_create io2;
bool ret = true;
const char *fname1 = BASEDIR "\\lease_break_test4.dat";
struct smb2_tree *trees2[32] = { NULL, };
size_t i;
struct smb2_transport *transport1 = tree1->session->transport;
struct smbcli_options transport2_options;
struct smb2_session *session1 = tree1->session;
uint16_t local_port = 0;
struct smb2_lease ls1;
struct smb2_lease ls2;
bool block_setup = false;
bool block_ok = false;
double open_duration;
if (!test_multichannel_initial_checks(tctx, tree1)) {
return true;
}
torture_comment(tctx, "Lease break retry: Test4\n");
torture_reset_lease_break_info(tctx, &lease_break_info);
lease_break_info.lease_skip_ack = true;
transport1->lease.handler = torture_lease_handler;
transport1->lease.private_data = tree1;
torture_comment(tctx, "transport1 [%p]\n", transport1);
local_port = torture_get_local_port_from_transport(transport1);
torture_comment(tctx, "transport1 uses tcp port: %d\n", local_port);
status = torture_smb2_testdir(tree1, BASEDIR, &_h);
CHECK_STATUS(status, NT_STATUS_OK);
smb2_util_close(tree1, _h);
smb2_util_unlink(tree1, fname1);
CHECK_VAL(lease_break_info.count, 0);
smb2_lease_v2_create(&io2, &ls2, false, fname1,
LEASE2F1, NULL,
smb2_util_lease_state("RHW"),
0x20);
transport2_options = transport1->options;
ret = test_multichannel_create_channel_array(tctx, host, share, credentials,
&transport2_options,
ARRAY_SIZE(trees2), trees2);
torture_assert(tctx, ret, "Could not create channels.\n");
for (i = 0; i < ARRAY_SIZE(trees2); i++) {
struct test_multichannel_lease_break_channel *c = &state.channels[i];
struct smb2_transport *t = trees2[i]->session->transport;
c->state = &state;
c->idx = i+1;
c->tree = trees2[i];
snprintf(c->name, sizeof(c->name), "trees2_%zu", c->idx);
t->lease.handler = test_multichannel_lease_break_handler;
t->lease.private_data = c;
}
open2_channel = &state.channels[0];
/* 2a opens file1 */
torture_comment(tctx, "client2 opens fname1 via %s\n",
open2_channel->name);
status = smb2_create(open2_channel->tree, mem_ctx, &io2);
CHECK_STATUS(status, NT_STATUS_OK);
h_client2_file1 = io2.out.file.handle;
CHECK_CREATED(&io2, CREATED, FILE_ATTRIBUTE_ARCHIVE);
CHECK_LEASE_V2(&io2, "RHW", true, LEASE2F1, 0, 0, 0x21);
CHECK_VAL(io2.out.durable_open_v2, false);
CHECK_VAL(io2.out.timeout, io2.in.timeout);
CHECK_VAL(io2.out.durable_open, false);
CHECK_VAL(lease_break_info.count, 0);
block_setup = test_setup_blocked_transports(tctx);
torture_assert(tctx, block_setup, "test_setup_blocked_transports");
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
struct test_multichannel_lease_break_channel *c = &state.channels[i];
struct smb2_transport *t = c->tree->session->transport;
torture_comment(tctx, "Blocking %s\n", c->name);
block_ok = _test_block_smb2_transport(tctx, t, c->name);
torture_assert_goto(tctx, block_ok, ret, done, "we could not block tcp transport");
c->blocked = true;
}
/* 1 opens file2 */
torture_comment(tctx,
"Client opens fname1 with session 1 with all %zu blocked\n",
ARRAY_SIZE(trees2));
smb2_lease_v2_create(&io1, &ls1, false, fname1,
LEASE1F1, NULL,
smb2_util_lease_state("RHW"),
0x10);
CHECK_VAL(lease_break_info.count, 0);
state.open_req_time = timeval_current();
state.last_break_time = state.open_req_time;
status = smb2_create(tree1, mem_ctx, &io1);
state.open_rep_time = timeval_current();
CHECK_STATUS(status, NT_STATUS_OK);
h_client1_file1 = io1.out.file.handle;
CHECK_VAL_GREATER_THAN(lease_break_info.count, 1);
open_duration = timeval_elapsed2(&state.open_req_time,
&state.open_rep_time);
torture_comment(tctx, "open_duration: %f\n", open_duration);
if (lease_break_info.count < ARRAY_SIZE(state.channels)) {
CHECK_VAL_GREATER_THAN(open_duration, 35);
CHECK_LEASE_V2(&io1, "RH", true, LEASE1F1, 0, 0, 0x11);
} else {
CHECK_LEASE_V2(&io1, "RWH", true, LEASE1F1, 0, 0, 0x11);
}
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
if (lease_break_info.count >= ARRAY_SIZE(state.channels)) {
break;
}
torture_comment(tctx, "Received %d lease break(s) wait for more!!\n",
lease_break_info.count);
torture_wait_for_lease_break(tctx);
}
if (lease_break_info.count == 0) {
torture_comment(tctx,
"Did not receive expected lease break!!\n");
} else {
torture_comment(tctx, "Received %d lease break(s)!!\n",
lease_break_info.count);
}
if (lease_break_info.count < ARRAY_SIZE(state.channels)) {
CHECK_VAL_GREATER_THAN(lease_break_info.count, 3);
} else {
CHECK_VAL(lease_break_info.count, ARRAY_SIZE(state.channels));
}
for (i = 0; i < lease_break_info.count; i++) {
struct test_multichannel_lease_break_channel *c = &state.channels[i];
torture_comment(tctx, "Verify %s\n", c->name);
torture_assert_int_equal(tctx, c->break_num, c->idx,
"Got lease break in wrong order");
CHECK_LEASE_BREAK_V2(c->lb, LEASE2F1, "RWH", "RH",
SMB2_NOTIFY_BREAK_LEASE_FLAG_ACK_REQUIRED,
0x22);
}
done:
for (i = 0; i < ARRAY_SIZE(state.channels); i++) {
struct test_multichannel_lease_break_channel *c = &state.channels[i];
struct smb2_transport *t = NULL;
if (!c->blocked) {
continue;
}
t = c->tree->session->transport;
torture_comment(tctx, "Unblocking %s\n", c->name);
_test_unblock_smb2_transport(tctx, t, c->name);
c->blocked = false;
}
if (block_setup) {
test_cleanup_blocked_transports(tctx);
}
tree1->session = session1;
smb2_util_close(tree1, h_client1_file1);
if (trees2[0] != NULL) {
smb2_util_close(trees2[0], h_client2_file1);
}
if (h != NULL) {
smb2_util_close(tree1, *h);
}
smb2_util_unlink(tree1, fname1);
smb2_deltree(tree1, BASEDIR);
for (i = 0; i < ARRAY_SIZE(trees2); i++) {
if (trees2[i] == NULL) {
continue;
}
TALLOC_FREE(trees2[i]);
}
talloc_free(tree1);
talloc_free(mem_ctx);
return ret;
}
/*
* Test channel merging race
* This is a regression test for
* https://bugzilla.samba.org/show_bug.cgi?id=15346
*/
struct test_multichannel_bug_15346_conn;
struct test_multichannel_bug_15346_state {
struct torture_context *tctx;
struct test_multichannel_bug_15346_conn *conns;
size_t num_conns;
size_t num_ready;
bool asserted;
bool looping;
};
struct test_multichannel_bug_15346_conn {
struct test_multichannel_bug_15346_state *state;
size_t idx;
struct smbXcli_conn *smbXcli;
struct tevent_req *nreq;
struct tevent_req *ereq;
};
static void test_multichannel_bug_15346_ndone(struct tevent_req *subreq);
static void test_multichannel_bug_15346_edone(struct tevent_req *subreq);
static void test_multichannel_bug_15346_ndone(struct tevent_req *subreq)
{
struct test_multichannel_bug_15346_conn *conn =
(struct test_multichannel_bug_15346_conn *)
tevent_req_callback_data_void(subreq);
struct test_multichannel_bug_15346_state *state = conn->state;
struct torture_context *tctx = state->tctx;
struct timeval current_time;
struct tm tm_buf;
struct tm *current_tm = NULL;
char time_str[sizeof "10000-01-01T00:00:00"];
size_t time_str_len;
NTSTATUS status;
bool ok = false;
SMB_ASSERT(conn->nreq == subreq);
conn->nreq = NULL;
status = smbXcli_negprot_recv(subreq, NULL, NULL);
TALLOC_FREE(subreq);
torture_assert_ntstatus_ok_goto(tctx, status, ok, asserted,
"smbXcli_negprot_recv failed");
current_time = tevent_timeval_current();
current_tm = gmtime_r(&current_time.tv_sec, &tm_buf);
torture_assert_not_null_goto(tctx, current_tm, ok, asserted,
"gmtime_r failed");
time_str_len = strftime(time_str, sizeof time_str, "%FT%T", current_tm);
torture_assert_size_not_equal_goto(tctx, time_str_len, 0, ok, asserted,
"strftime failed");
torture_comment(tctx,
"%s.%ldZ: conn[%zu]: negprot done\n",
time_str,
(long)current_time.tv_usec,
conn->idx);
conn->ereq = smb2cli_echo_send(conn->smbXcli,
tctx->ev,
conn->smbXcli,
state->num_conns * 2 * 1000);
torture_assert_goto(tctx, conn->ereq != NULL, ok, asserted,
"smb2cli_echo_send");
tevent_req_set_callback(conn->ereq,
test_multichannel_bug_15346_edone,
conn);
return;
asserted:
SMB_ASSERT(!ok);
state->asserted = true;
state->looping = false;
return;
}
static void test_multichannel_bug_15346_edone(struct tevent_req *subreq)
{
struct test_multichannel_bug_15346_conn *conn =
(struct test_multichannel_bug_15346_conn *)
tevent_req_callback_data_void(subreq);
struct test_multichannel_bug_15346_state *state = conn->state;
struct torture_context *tctx = state->tctx;
struct timeval current_time;
struct tm tm_buf;
struct tm *current_tm = NULL;
char time_str[sizeof "10000-01-01T00:00:00"];
size_t time_str_len;
const char *outcome = NULL;
NTSTATUS status;
bool ok = false;
SMB_ASSERT(conn->ereq == subreq);
conn->ereq = NULL;
current_time = tevent_timeval_current();
current_tm = gmtime_r(&current_time.tv_sec, &tm_buf);
torture_assert_not_null_goto(tctx, current_tm, ok, asserted,
"gmtime_r failed");
time_str_len = strftime(time_str, sizeof time_str, "%FT%T", current_tm);
torture_assert_size_not_equal_goto(tctx, time_str_len, 0, ok, asserted,
"strftime failed");
status = smb2cli_echo_recv(subreq);
TALLOC_FREE(subreq);
if (NT_STATUS_EQUAL(status, NT_STATUS_IO_TIMEOUT)) {
outcome = "timed out";
} else if (!NT_STATUS_IS_OK(status)) {
outcome = "failed";
} else {
outcome = "done";
}
torture_comment(tctx,
"%s.%ldZ: conn[%zu]: echo %s\n",
time_str,
(long)current_time.tv_usec,
conn->idx,
outcome);
torture_assert_ntstatus_ok_goto(tctx, status, ok, asserted,
"smb2cli_echo_recv failed");
state->num_ready += 1;
if (state->num_ready < state->num_conns) {
return;
}
state->looping = false;
return;
asserted:
SMB_ASSERT(!ok);
state->asserted = true;
state->looping = false;
return;
}
static bool test_multichannel_bug_15346(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 resolve_context *resolve_ctx = lpcfg_resolve_context(tctx->lp_ctx);
const char *socket_options = lpcfg_socket_options(tctx->lp_ctx);
struct gensec_settings *gsettings = NULL;
bool ret = true;
NTSTATUS status;
struct smb2_transport *transport1 = tree1->session->transport;
struct test_multichannel_bug_15346_state *state = NULL;
uint32_t server_capabilities;
struct smb2_handle root_handle = {{0}};
size_t i;
if (smbXcli_conn_protocol(transport1->conn) < PROTOCOL_SMB3_00) {
torture_fail(tctx,
"SMB 3.X Dialect family required for Multichannel"
" tests\n");
}
server_capabilities = smb2cli_conn_server_capabilities(
tree1->session->transport->conn);
if (!(server_capabilities & SMB2_CAP_MULTI_CHANNEL)) {
torture_fail(tctx,
"Server does not support multichannel.");
}
torture_comment(tctx, "Testing for BUG 15346\n");
state = talloc_zero(tctx, struct test_multichannel_bug_15346_state);
torture_assert_goto(tctx, state != NULL, ret, done,
"talloc_zero");
state->tctx = tctx;
gsettings = lpcfg_gensec_settings(state, tctx->lp_ctx);
torture_assert_goto(tctx, gsettings != NULL, ret, done,
"lpcfg_gensec_settings");
/*
* 32 is the W2K12R2 and W2K16 limit
* add 31 additional connections
*/
state->num_conns = 31;
state->conns = talloc_zero_array(state,
struct test_multichannel_bug_15346_conn,
state->num_conns);
torture_assert_goto(tctx, state->conns != NULL, ret, done,
"talloc_zero_array");
/*
* First we open the additional tcp connections
*/
for (i = 0; i < state->num_conns; i++) {
struct test_multichannel_bug_15346_conn *conn = &state->conns[i];
struct socket_context *sock = NULL;
uint16_t port = 445;
struct smbcli_options options = transport1->options;
conn->state = state;
conn->idx = i;
status = socket_connect_multi(state->conns,
host,
1, &port,
resolve_ctx,
tctx->ev,
&sock,
&port);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"socket_connect_multi failed");
conn->smbXcli = smbXcli_conn_create(state->conns,
sock->fd,
host,
SMB_SIGNING_OFF,
0,
&options.client_guid,
options.smb2_capabilities,
&options.smb3_capabilities);
torture_assert_goto(tctx, conn->smbXcli != NULL, ret, done,
"smbXcli_conn_create failed");
sock->fd = -1;
TALLOC_FREE(sock);
}
/*
* Now prepare the async SMB2 Negotiate requests
*/
for (i = 0; i < state->num_conns; i++) {
struct test_multichannel_bug_15346_conn *conn = &state->conns[i];
conn->nreq = smbXcli_negprot_send(conn->smbXcli,
tctx->ev,
conn->smbXcli,
state->num_conns * 2 * 1000,
smbXcli_conn_protocol(transport1->conn),
smbXcli_conn_protocol(transport1->conn),
33, /* max_credits */
NULL);
torture_assert_goto(tctx, conn->nreq != NULL, ret, done, "smbXcli_negprot_send");
tevent_req_set_callback(conn->nreq,
test_multichannel_bug_15346_ndone,
conn);
}
/*
* now we loop until all negprot and the first round
* of echos are done.
*/
state->looping = true;
while (state->looping) {
torture_assert_goto(tctx, tevent_loop_once(tctx->ev) == 0,
ret, done, "tevent_loop_once");
}
if (state->asserted) {
ret = false;
goto done;
}
/*
* Now we check that the connections are still usable
*/
for (i = 0; i < state->num_conns; i++) {
struct test_multichannel_bug_15346_conn *conn = &state->conns[i];
torture_comment(tctx, "conn[%zu]: checking echo again1\n", conn->idx);
status = smb2cli_echo(conn->smbXcli, 1000);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2cli_echo failed");
}
status = smb2_util_roothandle(tree1, &root_handle);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_util_roothandle failed");
/*
* Now we check that the connections are still usable
*/
for (i = 0; i < state->num_conns; i++) {
struct test_multichannel_bug_15346_conn *conn = &state->conns[i];
struct smbcli_options options = transport1->options;
struct smb2_session *session = NULL;
struct smb2_tree *tree = NULL;
union smb_fileinfo io;
torture_comment(tctx, "conn[%zu]: checking session bind\n", conn->idx);
/*
* Prepare smb2_{tree,session,transport} structures
* for the existing connection.
*/
options.only_negprot = true;
status = smb2_connect_ext(state->conns,
host,
NULL, /* ports */
share,
resolve_ctx,
samba_cmdline_get_creds(),
&conn->smbXcli,
0, /* previous_session_id */
&tree,
tctx->ev,
&options,
socket_options,
gsettings);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_connect_ext failed");
conn->smbXcli = tree->session->transport->conn;
session = smb2_session_channel(tree->session->transport,
lpcfg_gensec_settings(tree, tctx->lp_ctx),
tree,
tree1->session);
torture_assert_goto(tctx, session != NULL, ret, done,
"smb2_session_channel failed");
status = smb2_session_setup_spnego(session,
samba_cmdline_get_creds(),
0 /* previous_session_id */);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_session_setup_spnego failed");
/*
* Fix up the bound smb2_tree
*/
tree->session = session;
tree->smbXcli = tree1->smbXcli;
ZERO_STRUCT(io);
io.generic.level = RAW_FILEINFO_SMB2_ALL_INFORMATION;
io.generic.in.file.handle = root_handle;
status = smb2_getinfo_file(tree, tree, &io);
torture_assert_ntstatus_ok_goto(tctx, status, ret, done,
"smb2_getinfo_file failed");
}
done:
talloc_free(state);
return ret;
}
struct torture_suite *torture_smb2_multichannel_init(TALLOC_CTX *ctx)
{
struct torture_suite *suite = torture_suite_create(ctx, "multichannel");
struct torture_suite *suite_generic = torture_suite_create(ctx,
"generic");
struct torture_suite *suite_oplocks = torture_suite_create(ctx,
"oplocks");
struct torture_suite *suite_leases = torture_suite_create(ctx,
"leases");
struct torture_suite *suite_bugs = torture_suite_create(ctx,
"bugs");
torture_suite_add_suite(suite, suite_generic);
torture_suite_add_suite(suite, suite_oplocks);
torture_suite_add_suite(suite, suite_leases);
torture_suite_add_suite(suite, suite_bugs);
torture_suite_add_1smb2_test(suite_generic, "interface_info",
test_multichannel_interface_info);
torture_suite_add_1smb2_test(suite_generic, "num_channels",
test_multichannel_num_channels);
torture_suite_add_1smb2_test(suite_oplocks, "test1",
test_multichannel_oplock_break_test1);
torture_suite_add_1smb2_test(suite_oplocks, "test2",
test_multichannel_oplock_break_test2);
torture_suite_add_1smb2_test(suite_oplocks, "test3_windows",
test_multichannel_oplock_break_test3_windows);
torture_suite_add_1smb2_test(suite_oplocks, "test3_specification",
test_multichannel_oplock_break_test3_specification);
torture_suite_add_1smb2_test(suite_leases, "test1",
test_multichannel_lease_break_test1);
torture_suite_add_1smb2_test(suite_leases, "test2",
test_multichannel_lease_break_test2);
torture_suite_add_1smb2_test(suite_leases, "test3",
test_multichannel_lease_break_test3);
torture_suite_add_1smb2_test(suite_leases, "test4",
test_multichannel_lease_break_test4);
torture_suite_add_1smb2_test(suite_bugs, "bug_15346",
test_multichannel_bug_15346);
suite->description = talloc_strdup(suite, "SMB2 Multichannel tests");
return suite;
}