1089 lines
25 KiB
C
1089 lines
25 KiB
C
/* $NetBSD: iscsi_test.c,v 1.2 2011/11/29 03:50:31 tls Exp $ */
|
|
|
|
/*-
|
|
* Copyright (c) 2006,2011 The NetBSD Foundation, Inc.
|
|
* All rights reserved.
|
|
*
|
|
* This code is derived from software contributed to The NetBSD Foundation
|
|
* by Wasabi Systems, Inc.
|
|
*
|
|
* Redistribution and use in source and binary forms, with or without
|
|
* modification, are permitted provided that the following conditions
|
|
* are met:
|
|
* 1. Redistributions of source code must retain the above copyright
|
|
* notice, this list of conditions and the following disclaimer.
|
|
* 2. Redistributions in binary form must reproduce the above copyright
|
|
* notice, this list of conditions and the following disclaimer in the
|
|
* documentation and/or other materials provided with the distribution.
|
|
*
|
|
* THIS SOFTWARE IS PROVIDED BY THE NETBSD FOUNDATION, INC. AND CONTRIBUTORS
|
|
* ``AS IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
|
|
* TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
|
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE FOUNDATION OR CONTRIBUTORS
|
|
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
|
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
|
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
|
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
|
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
|
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
|
* POSSIBILITY OF SUCH DAMAGE.
|
|
*/
|
|
|
|
#include "iscsi_globals.h"
|
|
|
|
#ifdef ISCSI_TEST_MODE
|
|
|
|
STATIC test_pars_list_t test_list = TAILQ_HEAD_INITIALIZER(test_list);
|
|
|
|
/* ------------------- Local Stuff for PDU modifications -------------------- */
|
|
|
|
/*
|
|
* find_pdu_offset:
|
|
* Return pointer to PDU location to be modified, based on io vector.
|
|
* Also return maximum size of field.
|
|
*
|
|
* Parameter:
|
|
* pdu the PDU to modify
|
|
* offset the offset into the PDU
|
|
* ptr OUT: The pointer to the data
|
|
* maxlen OUT: Maximum length of field
|
|
*
|
|
* Returns 0 on success, nonzero if offset exceeds PDU size.
|
|
*/
|
|
|
|
STATIC int
|
|
find_pdu_offset(pdu_t *pdu, int offset, uint8_t **ptr, int *maxlen)
|
|
{
|
|
uint8_t *pt = NULL;
|
|
int i, maxl = 0;
|
|
|
|
for (i = 0; i < pdu->uio.uio_iovcnt; i++) {
|
|
pt = pdu->io_vec[i].iov_base;
|
|
maxl = pdu->io_vec[i].iov_len;
|
|
|
|
if (offset >= maxl) {
|
|
offset -= maxl;
|
|
} else {
|
|
maxl -= offset;
|
|
pt += offset;
|
|
offset = -1;
|
|
break;
|
|
}
|
|
}
|
|
DEB(1, ("Find pdu offset: offs=%d, ptr=%x, maxl=%d\n",
|
|
offset, (int) pt, maxl));
|
|
|
|
if (offset >= 0 || !maxl)
|
|
return 1;
|
|
|
|
*ptr = pt;
|
|
*maxlen = maxl;
|
|
return 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* mod_pdu:
|
|
* Modify given PDU according to modification descriptor.
|
|
*
|
|
* Parameter:
|
|
* pdu the PDU to modify
|
|
* mp the modification descriptor
|
|
*/
|
|
|
|
STATIC void
|
|
mod_pdu(pdu_t *pdu, iscsi_pdu_mod_t *mp)
|
|
{
|
|
int add = mp->flags & ISCSITEST_MOD_FLAG_ADD_VAL;
|
|
int64_t val = *((int64_t *) mp->value);
|
|
uint8_t *ptr;
|
|
int len;
|
|
|
|
switch (mp->offset) {
|
|
case ISCSITEST_OFFSET_DATADIGEST:
|
|
ptr = (uint8_t *) &pdu->data_digest;
|
|
len = sizeof(pdu->data_digest);
|
|
break;
|
|
|
|
case ISCSITEST_OFFSET_HEADERDIGEST:
|
|
ptr = (uint8_t *) &pdu->pdu.HeaderDigest;
|
|
len = sizeof(pdu->pdu.HeaderDigest);
|
|
break;
|
|
|
|
case ISCSITEST_OFFSET_DATA:
|
|
if ((len = pdu->io_vec[1].iov_len) == 0)
|
|
return;
|
|
ptr = pdu->io_vec[1].iov_base;
|
|
break;
|
|
|
|
case ISCSITEST_OFFSET_DRV_CMDSN:
|
|
ptr = (uint8_t *) &pdu->connection->session->CmdSN;
|
|
len = sizeof(pdu->connection->session->CmdSN);
|
|
break;
|
|
|
|
default:
|
|
if (find_pdu_offset(pdu, mp->offset, &ptr, &len))
|
|
return;
|
|
break;
|
|
}
|
|
|
|
len = min(len, mp->size);
|
|
|
|
DEB(1, ("mod_pdu: mpoff=%d, size=%d, len=%d, val=%qx, *ptr=%qx\n",
|
|
mp->offset, mp->size, len, val, *((uint64_t *) ptr)));
|
|
|
|
if (!add) {
|
|
if (mp->flags & ISCSITEST_MOD_FLAG_REORDER)
|
|
val = htonq(val);
|
|
memcpy(ptr, &val, len);
|
|
} else if (len == mp->size) {
|
|
switch (len) {
|
|
case 1:
|
|
*ptr += *mp->value;
|
|
break;
|
|
case 2:
|
|
val += (int64_t) (ntohs(*((uint16_t *) ptr)));
|
|
*((uint16_t *) ptr) = htons((uint16_t) val);
|
|
break;
|
|
case 3:
|
|
val += (int64_t) (ntoh3(ptr));
|
|
hton3((uint32_t) val, ptr);
|
|
break;
|
|
case 4:
|
|
val += (int64_t) (ntohl(*((uint32_t *) ptr)));
|
|
*((uint32_t *) ptr) = htonl((uint32_t) val);
|
|
break;
|
|
case 8:
|
|
val += ntohq(*((uint64_t *) ptr));
|
|
*((uint64_t *) ptr) = htonq(val);
|
|
break;
|
|
|
|
default:
|
|
break;
|
|
}
|
|
}
|
|
DEB(1, ("mod_pdu: *ptr=%qx\n", *((uint64_t *) ptr)));
|
|
}
|
|
|
|
|
|
/*
|
|
* update_options:
|
|
* Update test options from current mod record.
|
|
*
|
|
* Parameter:
|
|
* tp the test parameters
|
|
* mod the mod descriptor
|
|
*/
|
|
|
|
STATIC void
|
|
update_options(test_pars_t *tp, mod_desc_t *mod)
|
|
{
|
|
if ((tp->options & ISCSITEST_OPT_DISABLE_CCB_TIMEOUT) &&
|
|
(mod->pars.options & ISCSITEST_OPT_ENABLE_CCB_TIMEOUT))
|
|
tp->options &= ~ISCSITEST_OPT_DISABLE_CCB_TIMEOUT;
|
|
else if (!(tp->options & ISCSITEST_OPT_DISABLE_CCB_TIMEOUT) &&
|
|
(mod->pars.options & ISCSITEST_OPT_DISABLE_CCB_TIMEOUT))
|
|
tp->options |= ISCSITEST_OPT_DISABLE_CCB_TIMEOUT;
|
|
|
|
if ((tp->options & ISCSITEST_OPT_DISABLE_CONN_TIMEOUT) &&
|
|
(mod->pars.options & ISCSITEST_OPT_ENABLE_CONN_TIMEOUT))
|
|
tp->options &= ~ISCSITEST_OPT_DISABLE_CONN_TIMEOUT;
|
|
else if (!(tp->options & ISCSITEST_OPT_DISABLE_CONN_TIMEOUT) &&
|
|
(mod->pars.options & ISCSITEST_OPT_DISABLE_CONN_TIMEOUT))
|
|
tp->options |= ISCSITEST_OPT_DISABLE_CONN_TIMEOUT;
|
|
|
|
if (mod->pars.options & ISCSITEST_OPT_USE_RANDOM_TX)
|
|
tp->lose_random[CNT_TX] = mod->pars.lose_random_tx;
|
|
if (mod->pars.options & ISCSITEST_OPT_USE_RANDOM_RX)
|
|
tp->lose_random[CNT_RX] = mod->pars.lose_random_rx;
|
|
}
|
|
|
|
|
|
/*
|
|
* test_get:
|
|
* Copy PDU to app
|
|
*
|
|
* Parameter:
|
|
* pdu the PDU to modify
|
|
* mod the modification descriptor
|
|
* error the PDU error indication
|
|
*/
|
|
|
|
STATIC void
|
|
test_get(pdu_t *pdu, mod_desc_t *mod, int error)
|
|
{
|
|
uint32_t size, cpy, dlen;
|
|
uint8_t *pptr;
|
|
|
|
pptr = mod->pdu_ptr;
|
|
size = mod->pars.pdu_size;
|
|
|
|
cpy = min(size, BHS_SIZE);
|
|
memcpy(pptr, &pdu->pdu, cpy);
|
|
size -= cpy;
|
|
pptr += cpy;
|
|
dlen = pdu->save_uio.uio_resid;
|
|
|
|
if (size && dlen) {
|
|
cpy = min(size, dlen);
|
|
memcpy(pptr, (char *) pdu->save_uio.uio_iov, cpy);
|
|
size -= cpy;
|
|
}
|
|
mod->pars.pdu_actual_size = BHS_SIZE + dlen;
|
|
|
|
switch (error) {
|
|
case 0:
|
|
mod->pars.status = ISCSI_STATUS_SUCCESS;
|
|
break;
|
|
|
|
case TEST_INVALID_HEADER_CRC:
|
|
mod->pars.status = ISCSI_STATUS_TEST_HEADER_CRC_ERROR;
|
|
break;
|
|
|
|
case TEST_INVALID_DATA_CRC:
|
|
mod->pars.status = ISCSI_STATUS_TEST_DATA_CRC_ERROR;
|
|
break;
|
|
|
|
case TEST_READ_ERROR:
|
|
mod->pars.status = ISCSI_STATUS_TEST_DATA_READ_ERROR;
|
|
break;
|
|
|
|
default:
|
|
mod->pars.status = ISCSI_STATUS_GENERAL_ERROR;
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* check_loss:
|
|
* Check whether PDU is to be discarded randomly.
|
|
*
|
|
* Parameter:
|
|
* tp the test parameters
|
|
* rxtx whether this is a recieve or send PDU (CNT_TX or CNT_RX)
|
|
*
|
|
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
|
|
*/
|
|
|
|
STATIC int
|
|
check_loss(test_pars_t *tp, int rxtx)
|
|
{
|
|
return (tp->lose_random[rxtx]) ?
|
|
(cprng_fast32() % tp->lose_random[rxtx]) : 0;
|
|
}
|
|
|
|
|
|
/*
|
|
* test_mod:
|
|
* Check whether PDU is to be modified - calculate offset, modify PDU
|
|
* on match.
|
|
*
|
|
* Parameter:
|
|
* tp the test parameters
|
|
* pdu the PDU to modify
|
|
* kind the PDU kind
|
|
* rxtx whether this is a recieve or send PDU (CNT_TX or CNT_RX)
|
|
* err the PDU error indicator (set on digest/read errors, rx only)
|
|
*
|
|
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
|
|
*/
|
|
|
|
STATIC int
|
|
test_mod(test_pars_t *tp, pdu_t *pdu, iscsi_pdu_kind_t kind, int rxtx, int err)
|
|
{
|
|
mod_desc_t *mod;
|
|
uint32_t mpoff, off;
|
|
int i, rc = 0;
|
|
|
|
tp->pdu_count[kind][rxtx]++;
|
|
tp->pdu_count[ANY_PDU][rxtx]++;
|
|
|
|
do {
|
|
if ((mod = TAILQ_FIRST(&tp->mods)) == NULL) {
|
|
return check_loss(tp, rxtx);
|
|
}
|
|
if (mod->pars.which_pdu != ANY_PDU &&
|
|
mod->pars.which_pdu != kind) {
|
|
return check_loss(tp, rxtx);
|
|
}
|
|
mpoff = mod->pars.pdu_offset;
|
|
|
|
switch (mod->pars.which_offset) {
|
|
case ABSOLUTE_ANY:
|
|
off = tp->pdu_count[ANY_PDU][CNT_TX] +
|
|
tp->pdu_count[ANY_PDU][CNT_RX];
|
|
break;
|
|
case RELATIVE_ANY:
|
|
off = (tp->pdu_count[ANY_PDU][CNT_TX] +
|
|
tp->pdu_count[ANY_PDU][CNT_RX]) -
|
|
(tp->pdu_last[ANY_PDU][CNT_TX] + tp->pdu_last[ANY_PDU][CNT_RX]);
|
|
break;
|
|
|
|
case ABSOLUTE_PDUKIND:
|
|
off = tp->pdu_count[kind][rxtx];
|
|
break;
|
|
case RELATIVE_PDUKIND:
|
|
off = tp->pdu_count[kind][rxtx] - tp->pdu_last[kind][rxtx];
|
|
break;
|
|
|
|
case ABSOLUTE_TX:
|
|
if (rxtx != CNT_TX)
|
|
return check_loss(tp, rxtx);
|
|
off = tp->pdu_count[ANY_PDU][CNT_TX];
|
|
break;
|
|
case RELATIVE_TX:
|
|
if (rxtx != CNT_TX)
|
|
return check_loss(tp, rxtx);
|
|
off = tp->pdu_count[ANY_PDU][CNT_TX] -
|
|
tp->pdu_last[ANY_PDU][CNT_TX];
|
|
break;
|
|
|
|
case ABSOLUTE_RX:
|
|
if (rxtx != CNT_RX)
|
|
return check_loss(tp, rxtx);
|
|
off = tp->pdu_count[ANY_PDU][CNT_RX];
|
|
break;
|
|
case RELATIVE_RX:
|
|
if (rxtx != CNT_RX)
|
|
return check_loss(tp, rxtx);
|
|
off = tp->pdu_count[ANY_PDU][CNT_RX] -
|
|
tp->pdu_last[ANY_PDU][CNT_RX];
|
|
break;
|
|
|
|
default:
|
|
/* bad offset - skip this entry */
|
|
mpoff = off = 0;
|
|
break;
|
|
}
|
|
|
|
DEB(1, ("test_mod: kind=%d, rxtx=%d, pdukind=%d, mpoff=%d, "
|
|
"whichoff=%d, off=%d\n", kind, rxtx, mod->pars.which_pdu,
|
|
mpoff, mod->pars.which_offset, off));
|
|
|
|
if (!off || (mpoff != 0 && mpoff < off)) {
|
|
/* This might happen in some cases. Just discard the modification. */
|
|
CS_BEGIN;
|
|
TAILQ_REMOVE(&tp->mods, mod, link);
|
|
CS_END;
|
|
update_options(tp, mod);
|
|
|
|
if (mod->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION) {
|
|
mod->pars.status = ISCSI_STATUS_TEST_MODIFICATION_SKIPPED;
|
|
wakeup(mod);
|
|
}
|
|
free(mod, M_TEMP);
|
|
}
|
|
} while (mpoff && mpoff < off);
|
|
|
|
if (mpoff > off)
|
|
return check_loss(tp, rxtx);
|
|
|
|
DEB(1, ("test_mod: opt=%x, pdu_ptr=%x, num_mods=%d\n", mod->pars.options,
|
|
(int) mod->pdu_ptr, mod->pars.num_pdu_mods));
|
|
|
|
if (mod->pdu_ptr)
|
|
test_get(pdu, mod, err);
|
|
|
|
if (mod->pars.options & ISCSITEST_OPT_DISCARD_PDU)
|
|
rc = 1;
|
|
else if (check_loss(tp, rxtx))
|
|
rc = 1;
|
|
else if (mod->pars.num_pdu_mods) {
|
|
if (!(mod->pars.options & ISCSITEST_OPT_MOD_PERMANENT)) {
|
|
/*
|
|
* Note: if the PDU is later resent, the unmodified one will be
|
|
* used as resend_pdu restores the original io vector.
|
|
*/
|
|
pdu->mod_pdu = pdu->pdu;
|
|
pdu->io_vec[0].iov_base = &pdu->mod_pdu;
|
|
}
|
|
for (i = 0; i < mod->pars.num_pdu_mods; i++) {
|
|
mod_pdu(pdu, &mod->mods[i]);
|
|
}
|
|
}
|
|
|
|
if (rxtx == CNT_TX) {
|
|
if (mod->pars.options & ISCSITEST_OPT_NO_RESPONSE_PDU) {
|
|
ccb_t *ccb = pdu->owner;
|
|
|
|
DEB(1, ("test_mod: No response expected, completing CCB %x\n",
|
|
(int)ccb));
|
|
|
|
if (ccb != NULL &&
|
|
(ccb->disp == CCBDISP_WAIT || ccb->disp == CCBDISP_SCSIPI)) {
|
|
/* simulate timeout */
|
|
ccb->status = ISCSI_STATUS_TIMEOUT;
|
|
wake_ccb(ccb);
|
|
}
|
|
}
|
|
|
|
if ((mod->pars.options & ISCSITEST_SFLAG_UPDATE_FIELDS) &&
|
|
mod->pars.num_pdu_mods) {
|
|
connection_t *conn = pdu->connection;
|
|
|
|
if (conn->HeaderDigest &&
|
|
!(mod->pars.options & ISCSITEST_SFLAG_NO_HEADER_DIGEST))
|
|
pdu->pdu.HeaderDigest = gen_digest(&pdu->pdu, BHS_SIZE);
|
|
|
|
if (pdu->uio.uio_iovcnt > 1 && conn->DataDigest &&
|
|
!(mod->pars.options & ISCSITEST_SFLAG_NO_DATA_DIGEST))
|
|
pdu->data_digest = gen_digest_2(
|
|
pdu->io_vec[1].iov_base,
|
|
pdu->io_vec[1].iov_len,
|
|
pdu->io_vec[2].iov_base,
|
|
pdu->io_vec[2].iov_len);
|
|
}
|
|
}
|
|
|
|
CS_BEGIN;
|
|
TAILQ_REMOVE(&tp->mods, mod, link);
|
|
|
|
update_options(tp, mod);
|
|
|
|
/* we've modified a PDU - copy current count into last count */
|
|
memcpy(tp->pdu_last, tp->pdu_count, sizeof(tp->pdu_last));
|
|
CS_END;
|
|
if (mod->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION) {
|
|
wakeup(mod);
|
|
}
|
|
if (mod->pars.options & ISCSITEST_KILL_CONNECTION) {
|
|
kill_connection(tp->connection,
|
|
ISCSI_STATUS_TEST_CONNECTION_CLOSED,
|
|
NO_LOGOUT, TRUE);
|
|
}
|
|
free(mod, M_TEMP);
|
|
|
|
return rc;
|
|
}
|
|
|
|
|
|
/*
|
|
* free_negs:
|
|
* Free all negotiation elements attached to test parameter.
|
|
*
|
|
* Parameter:
|
|
* tp The test parameter
|
|
*/
|
|
|
|
STATIC void
|
|
free_negs(test_pars_t *tp)
|
|
{
|
|
neg_desc_t *np;
|
|
|
|
while ((np = TAILQ_FIRST(&tp->negs)) != NULL) {
|
|
TAILQ_REMOVE(&tp->negs, np, link);
|
|
free(np, M_TEMP);
|
|
}
|
|
}
|
|
|
|
|
|
/*
|
|
* free_mods:
|
|
* Free all modification elements attached to test parameter.
|
|
*
|
|
* Parameter:
|
|
* tp The test parameter
|
|
* status The status code to return for clients waiting on completion
|
|
*/
|
|
|
|
STATIC void
|
|
free_mods(test_pars_t *tp, uint32_t status)
|
|
{
|
|
mod_desc_t *mp;
|
|
|
|
while ((mp = TAILQ_FIRST(&tp->mods)) != NULL) {
|
|
TAILQ_REMOVE(&tp->mods, mp, link);
|
|
if (mp->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION) {
|
|
mp->pars.status = status;
|
|
wakeup(mp);
|
|
}
|
|
free(mp, M_TEMP);
|
|
}
|
|
}
|
|
|
|
|
|
/* ---------------- Global functions for PDU modifications ------------------ */
|
|
|
|
/*
|
|
* test_mode_rx:
|
|
* Check whether received PDU is to be modified - determine PDU kind,
|
|
* then call test_mod for further processing.
|
|
*
|
|
* Parameter:
|
|
* conn the connection
|
|
* pdu the PDU to modify
|
|
* error invalid frame if nonzero: don't do mods, frame will be
|
|
* discarded anyway.
|
|
*
|
|
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
|
|
*/
|
|
|
|
int
|
|
test_mode_rx(connection_t *conn, pdu_t *pdu, int error)
|
|
{
|
|
test_pars_t *tp;
|
|
iscsi_pdu_kind_t kind;
|
|
|
|
if ((tp = conn->test_pars) == NULL) {
|
|
return 0;
|
|
}
|
|
switch (pdu->pdu.Opcode & OPCODE_MASK) {
|
|
case TOP_NOP_In:
|
|
kind = NOP_IN_PDU;
|
|
break;
|
|
|
|
case TOP_SCSI_Response:
|
|
kind = RESPONSE_PDU;
|
|
break;
|
|
|
|
case TOP_SCSI_Task_Management:
|
|
kind = TASK_RSP_PDU;
|
|
break;
|
|
|
|
case TOP_Login_Response:
|
|
kind = LOGIN_RSP_PDU;
|
|
break;
|
|
|
|
case TOP_Text_Response:
|
|
kind = TEXT_RSP_PDU;
|
|
break;
|
|
|
|
case TOP_SCSI_Data_in:
|
|
kind = DATA_IN_PDU;
|
|
break;
|
|
|
|
case TOP_Logout_Response:
|
|
kind = LOGOUT_RSP_PDU;
|
|
break;
|
|
|
|
case TOP_R2T:
|
|
kind = R2T_PDU;
|
|
break;
|
|
|
|
case TOP_Asynchronous_Message:
|
|
kind = ASYNCH_PDU;
|
|
break;
|
|
|
|
case TOP_Reject:
|
|
kind = REJECT_PDU;
|
|
break;
|
|
|
|
default:
|
|
kind = INVALID_PDU;
|
|
break;
|
|
}
|
|
|
|
return test_mod(tp, pdu, kind, CNT_RX, error);
|
|
}
|
|
|
|
|
|
/*
|
|
* test_mode_tx:
|
|
* Check whether PDU to be sent is to be modified - determine PDU kind,
|
|
* then call test_mod for further processing.
|
|
*
|
|
* Parameter:
|
|
* conn the connection
|
|
* pdu the PDU to modify
|
|
*
|
|
* Returns nonzero if the PDU is to be discarded, 0 for normal processing.
|
|
*/
|
|
|
|
int
|
|
test_mode_tx(connection_t *conn, pdu_t *pdu)
|
|
{
|
|
test_pars_t *tp;
|
|
iscsi_pdu_kind_t kind;
|
|
|
|
if ((tp = conn->test_pars) == NULL)
|
|
return 0;
|
|
|
|
switch (pdu->pdu.Opcode & OPCODE_MASK) {
|
|
case IOP_NOP_Out:
|
|
kind = NOP_OUT_PDU;
|
|
break;
|
|
|
|
case IOP_SCSI_Command:
|
|
kind = COMMAND_PDU;
|
|
break;
|
|
|
|
case IOP_SCSI_Task_Management:
|
|
kind = TASK_REQ_PDU;
|
|
break;
|
|
|
|
case IOP_Login_Request:
|
|
kind = LOGIN_REQ_PDU;
|
|
break;
|
|
|
|
case IOP_Text_Request:
|
|
kind = TEXT_REQ_PDU;
|
|
break;
|
|
|
|
case IOP_SCSI_Data_out:
|
|
kind = DATA_OUT_PDU;
|
|
break;
|
|
|
|
case IOP_Logout_Request:
|
|
kind = LOGOUT_REQ_PDU;
|
|
break;
|
|
|
|
case IOP_SNACK_Request:
|
|
kind = SNACK_PDU;
|
|
break;
|
|
|
|
default:
|
|
kind = INVALID_PDU;
|
|
break;
|
|
}
|
|
|
|
return test_mod(tp, pdu, kind, CNT_TX, 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* test_assign_connection:
|
|
* Check whether there is an unassigned test parameter, and assign it to the
|
|
* given connection.
|
|
* Note: The number of concurrent tests at any time is assumed to be
|
|
* very small (usually 1-2). The algorithm used therefore can be very
|
|
* very simple minded.
|
|
*
|
|
* Parameter:
|
|
* conn the connection
|
|
*/
|
|
|
|
void
|
|
test_assign_connection(connection_t *conn)
|
|
{
|
|
test_pars_t *tp;
|
|
|
|
for (tp = TAILQ_FIRST(&test_list); tp != NULL; tp = TAILQ_NEXT(tp, link)) {
|
|
if (tp->connection == NULL) {
|
|
tp->connection = conn;
|
|
conn->test_pars = tp;
|
|
DEB(1, ("Assigning session %d, connection %d to test %d\n",
|
|
conn->session->id, conn->id, tp->test_id));
|
|
return;
|
|
}
|
|
}
|
|
}
|
|
|
|
/*
|
|
* test_remove_connection:
|
|
* If the connection has test parameters assigned, remove and free those
|
|
* parameters.
|
|
*
|
|
* Parameter:
|
|
* conn the connection
|
|
*/
|
|
|
|
void
|
|
test_remove_connection(connection_t *conn)
|
|
{
|
|
test_pars_t *tp;
|
|
int s;
|
|
|
|
s = splbio();
|
|
if ((tp = conn->test_pars) != NULL) {
|
|
conn->test_pars = NULL;
|
|
TAILQ_REMOVE(&test_list, tp, link);
|
|
splx(s);
|
|
DEB(9, ("Remove test %d from connection %d\n", tp->test_id, conn->id));
|
|
free_negs(tp);
|
|
free_mods(tp, ISCSI_STATUS_TEST_CONNECTION_CLOSED);
|
|
free(tp, M_TEMP);
|
|
} else {
|
|
splx(s);
|
|
}
|
|
}
|
|
|
|
|
|
/* ----------------------- Local Stuff for IOCTLs --------------------------- */
|
|
|
|
/*
|
|
* find_test_id:
|
|
* Search the given test ID in the test list.
|
|
*
|
|
* Parameter:
|
|
* id The test ID
|
|
*
|
|
* Returns a pointer to the test pars is found, else NULL.
|
|
*/
|
|
|
|
STATIC test_pars_t *
|
|
find_test_id(uint32_t id)
|
|
{
|
|
test_pars_t *curr;
|
|
|
|
for (curr = TAILQ_FIRST(&test_list);
|
|
curr && curr->test_id != id;
|
|
curr = TAILQ_NEXT(curr, link)) {
|
|
}
|
|
return curr;
|
|
}
|
|
|
|
|
|
/*
|
|
* add_neg:
|
|
* Add negotiation element to test parameter.
|
|
* Helper for test_define and test_add_neg.
|
|
*
|
|
* Parameter:
|
|
* tp The test parameter
|
|
* addr The pointer to the descriptor
|
|
* len The descriptor size
|
|
*
|
|
* Returns 0 on success, else an ISCSI status code.
|
|
*/
|
|
|
|
STATIC uint32_t
|
|
add_neg(test_pars_t *tp, void *addr, uint32_t len)
|
|
{
|
|
neg_desc_t *negp;
|
|
uint32_t size;
|
|
|
|
/* len already includes the size of iscsi_test_negotiation_descriptor_t */
|
|
size = len + sizeof(neg_desc_t) -
|
|
sizeof(iscsi_test_negotiation_descriptor_t);
|
|
if ((negp = malloc(size, M_TEMP, M_WAITOK)) == NULL) {
|
|
return ISCSI_STATUS_NO_RESOURCES;
|
|
}
|
|
copyin(addr, &negp->entry, len);
|
|
TAILQ_INSERT_TAIL(&tp->negs, negp, link);
|
|
DEB(1, ("add_neg: size %d, phase %d, flags %x, data %02x...\n",
|
|
negp->entry.size, negp->entry.state, negp->entry.flags,
|
|
negp->entry.value[0]));
|
|
return 0;
|
|
}
|
|
|
|
|
|
|
|
/* ----------------- Global functions for IOCTLs --------------------------- */
|
|
|
|
/*
|
|
* test_define:
|
|
* Implements ISCSI_TEST_DEFINE ioctl.
|
|
*
|
|
* Parameter:
|
|
* par The IOCTL parameter.
|
|
*/
|
|
|
|
void
|
|
test_define(iscsi_test_define_parameters_t *par)
|
|
{
|
|
test_pars_t *tp;
|
|
session_t *sess = NULL;
|
|
connection_t *conn = NULL;
|
|
|
|
if (!par->test_id) {
|
|
par->status = ISCSI_STATUS_INVALID_ID;
|
|
return;
|
|
}
|
|
if (find_test_id(par->test_id) != NULL) {
|
|
par->status = ISCSI_STATUS_DUPLICATE_ID;
|
|
return;
|
|
}
|
|
if (par->session_id &&
|
|
(sess = find_session(par->session_id)) == NULL) {
|
|
par->status = ISCSI_STATUS_INVALID_SESSION_ID;
|
|
return;
|
|
}
|
|
if (sess != NULL) {
|
|
if (par->connection_id &&
|
|
(conn = find_connection(sess, par->connection_id)) == NULL) {
|
|
par->status = ISCSI_STATUS_INVALID_CONNECTION_ID;
|
|
return;
|
|
} else if (!par->connection_id) {
|
|
conn = TAILQ_FIRST(&sess->conn_list);
|
|
}
|
|
if (conn->test_pars != NULL) {
|
|
par->status = ISCSI_STATUS_TEST_ALREADY_ASSIGNED;
|
|
return;
|
|
}
|
|
}
|
|
|
|
if ((tp = malloc(sizeof(*tp), M_TEMP, M_WAITOK | M_ZERO)) == NULL) {
|
|
par->status = ISCSI_STATUS_NO_RESOURCES;
|
|
return;
|
|
}
|
|
TAILQ_INIT(&tp->negs);
|
|
TAILQ_INIT(&tp->mods);
|
|
|
|
if (par->neg_descriptor_size &&
|
|
(par->status = add_neg(tp, par->neg_descriptor_ptr,
|
|
par->neg_descriptor_size)) != 0) {
|
|
free(tp, M_TEMP);
|
|
return;
|
|
}
|
|
|
|
tp->test_id = par->test_id;
|
|
tp->options = par->options;
|
|
tp->lose_random[CNT_RX] = par->lose_random_rx;
|
|
tp->lose_random[CNT_TX] = par->lose_random_tx;
|
|
tp->connection = conn;
|
|
tp->firstburst_val = par->firstburst_val;
|
|
tp->maxburst_val = par->maxburst_val;
|
|
tp->r2t_val = par->r2t_val;
|
|
|
|
DEB(1, ("TestDefine: id=%d, opt=%x, negsize=%d, conn=%x\n",
|
|
tp->test_id, tp->options, par->neg_descriptor_size, (int)conn));
|
|
|
|
TAILQ_INSERT_TAIL(&test_list, tp, link);
|
|
|
|
if (conn != NULL) {
|
|
tp->connection = conn;
|
|
conn->test_pars = tp;
|
|
DEB(1, ("Assigning session %d, connection %d to test %d\n",
|
|
conn->session->id, conn->id, tp->test_id));
|
|
}
|
|
|
|
par->status = ISCSI_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* test_add_neg:
|
|
* Implements ISCSI_TEST_ADD_NEGOTIATION ioctl.
|
|
*
|
|
* Parameter:
|
|
* par The IOCTL parameter.
|
|
*/
|
|
|
|
void
|
|
test_add_neg(iscsi_test_add_negotiation_parameters_t *par)
|
|
{
|
|
test_pars_t *tp;
|
|
|
|
if ((tp = find_test_id(par->test_id)) == NULL) {
|
|
par->status = ISCSI_STATUS_INVALID_ID;
|
|
return;
|
|
}
|
|
if (par->neg_descriptor_size <
|
|
sizeof(iscsi_test_negotiation_descriptor_t)) {
|
|
par->status = ISCSI_STATUS_PARAMETER_INVALID;
|
|
return;
|
|
}
|
|
par->status =
|
|
add_neg(tp, par->neg_descriptor_ptr, par->neg_descriptor_size);
|
|
}
|
|
|
|
|
|
/*
|
|
* test_add_mod:
|
|
* Implements ISCSI_TEST_ADD_MODIFICATION ioctl.
|
|
*
|
|
* Parameter:
|
|
* par The IOCTL parameter.
|
|
*/
|
|
|
|
void
|
|
test_add_mod(struct proc *p, iscsi_test_add_modification_parameters_t *par)
|
|
{
|
|
test_pars_t *tp;
|
|
mod_desc_t *modp;
|
|
uint32_t len;
|
|
uint32_t psize = par->pdu_size;
|
|
void *pdu_ptr = par->pdu_ptr;
|
|
|
|
if ((tp = find_test_id(par->test_id)) == NULL) {
|
|
par->status = ISCSI_STATUS_INVALID_ID;
|
|
return;
|
|
}
|
|
|
|
len = par->num_pdu_mods * sizeof(iscsi_pdu_mod_t);
|
|
|
|
if ((modp = malloc(len + sizeof(mod_desc_t),
|
|
M_TEMP, M_WAITOK | M_ZERO)) == NULL) {
|
|
par->status = ISCSI_STATUS_NO_RESOURCES;
|
|
return;
|
|
}
|
|
|
|
par->status = ISCSI_STATUS_SUCCESS;
|
|
|
|
modp->pars = *par;
|
|
if (len)
|
|
copyin(par->mod_ptr, modp->mods, len);
|
|
|
|
if (psize) {
|
|
if (pdu_ptr == NULL) {
|
|
free(modp, M_TEMP);
|
|
par->status = ISCSI_STATUS_PARAMETER_INVALID;
|
|
return;
|
|
}
|
|
|
|
if (pdu_ptr == NULL ||
|
|
(par->status = map_databuf(p, &pdu_ptr, psize)) != 0) {
|
|
free(modp, M_TEMP);
|
|
return;
|
|
}
|
|
|
|
modp->pdu_ptr = pdu_ptr;
|
|
/* force wait */
|
|
modp->pars.options |= ISCSITEST_OPT_WAIT_FOR_COMPLETION;
|
|
}
|
|
TAILQ_INSERT_TAIL(&tp->mods, modp, link);
|
|
|
|
if (modp->pars.options & ISCSITEST_OPT_WAIT_FOR_COMPLETION)
|
|
tsleep(modp, PWAIT, "test_completion", 0);
|
|
}
|
|
|
|
|
|
/*
|
|
* test_cancel:
|
|
* Implements ISCSI_TEST_CANCEL ioctl.
|
|
*
|
|
* Parameter:
|
|
* par The IOCTL parameter.
|
|
*/
|
|
|
|
void
|
|
test_cancel(iscsi_test_cancel_parameters_t *par)
|
|
{
|
|
test_pars_t *tp;
|
|
|
|
if ((tp = find_test_id(par->test_id)) == NULL) {
|
|
par->status = ISCSI_STATUS_INVALID_ID;
|
|
return;
|
|
}
|
|
DEB(1, ("Test Cancel, id %d\n", par->test_id));
|
|
|
|
CS_BEGIN;
|
|
if (tp->connection)
|
|
tp->connection->test_pars = NULL;
|
|
TAILQ_REMOVE(&test_list, tp, link);
|
|
CS_END;
|
|
free_negs(tp);
|
|
free_mods(tp, ISCSI_STATUS_TEST_CANCELED);
|
|
free(tp, M_TEMP);
|
|
par->status = ISCSI_STATUS_SUCCESS;
|
|
}
|
|
|
|
|
|
/*
|
|
* test_send_pdu:
|
|
* Implements ISCSI_TEST_SEND_PDU ioctl.
|
|
*
|
|
* Parameter:
|
|
* p The caller's context.
|
|
* par The IOCTL parameter.
|
|
*/
|
|
|
|
void
|
|
test_send_pdu(struct proc *p, iscsi_test_send_pdu_parameters_t *par)
|
|
{
|
|
static uint8_t pad_bytes[4] = { 0 };
|
|
test_pars_t *tp;
|
|
connection_t *conn;
|
|
pdu_t *pdu;
|
|
uint32_t psize = par->pdu_size;
|
|
void *pdu_ptr = par->pdu_ptr;
|
|
struct uio *uio;
|
|
uint32_t i, pad, dsl, size;
|
|
|
|
if ((tp = find_test_id(par->test_id)) == NULL) {
|
|
par->status = ISCSI_STATUS_INVALID_ID;
|
|
return;
|
|
}
|
|
if (!psize || pdu_ptr == NULL ||
|
|
((par->options & ISCSITEST_SFLAG_UPDATE_FIELDS) && psize < BHS_SIZE)) {
|
|
par->status = ISCSI_STATUS_PARAMETER_INVALID;
|
|
return;
|
|
}
|
|
if ((conn = tp->connection) == NULL) {
|
|
par->status = ISCSI_STATUS_TEST_INACTIVE;
|
|
return;
|
|
}
|
|
if ((pdu = get_pdu(conn)) == NULL) {
|
|
par->status = ISCSI_STATUS_TEST_CONNECTION_CLOSED;
|
|
return;
|
|
}
|
|
DEB(1, ("Test Send PDU, id %d\n", par->test_id));
|
|
|
|
if ((par->status = map_databuf(p, &pdu_ptr, psize)) != 0) {
|
|
free_pdu(pdu);
|
|
return;
|
|
}
|
|
|
|
i = 1;
|
|
if (!par->options) {
|
|
pdu->io_vec[0].iov_base = pdu_ptr;
|
|
pdu->io_vec[0].iov_len = size = psize;
|
|
} else {
|
|
memcpy(&pdu->pdu, pdu_ptr, BHS_SIZE);
|
|
|
|
if (!(pdu->pdu.Opcode & OP_IMMEDIATE))
|
|
conn->session->CmdSN++;
|
|
pdu->pdu.p.command.CmdSN = htonl(conn->session->CmdSN);
|
|
|
|
dsl = psize - BHS_SIZE;
|
|
size = BHS_SIZE;
|
|
|
|
hton3(dsl, pdu->pdu.DataSegmentLength);
|
|
|
|
if (conn->HeaderDigest &&
|
|
!(par->options & ISCSITEST_SFLAG_NO_HEADER_DIGEST)) {
|
|
pdu->pdu.HeaderDigest = gen_digest(&pdu->pdu, BHS_SIZE);
|
|
size += 4;
|
|
}
|
|
|
|
pdu->io_vec[0].iov_base = &pdu->pdu;
|
|
pdu->io_vec[0].iov_len = size;
|
|
|
|
if (dsl) {
|
|
pdu->io_vec[1].iov_base = &pdu_ptr[BHS_SIZE];
|
|
pdu->io_vec[1].iov_len = dsl;
|
|
i++;
|
|
size += dsl;
|
|
|
|
/* Pad to next multiple of 4 */
|
|
pad = (par->options & ISCSITEST_SFLAG_NO_PADDING) ? 0 : size & 0x03;
|
|
|
|
if (pad) {
|
|
pad = 4 - pad;
|
|
pdu->io_vec[i].iov_base = pad_bytes;
|
|
pdu->io_vec[i].iov_len = pad;
|
|
i++;
|
|
size += pad;
|
|
}
|
|
|
|
if (conn->DataDigest &&
|
|
!(par->options & ISCSITEST_SFLAG_NO_DATA_DIGEST)) {
|
|
pdu->data_digest = gen_digest_2(&pdu_ptr[BHS_SIZE], dsl,
|
|
pad_bytes, pad);
|
|
pdu->io_vec[i].iov_base = &pdu->data_digest;
|
|
pdu->io_vec[i].iov_len = 4;
|
|
i++;
|
|
size += 4;
|
|
}
|
|
}
|
|
}
|
|
uio = &pdu->uio;
|
|
uio->uio_iov = pdu->io_vec;
|
|
UIO_SETUP_SYSSPACE(uio);
|
|
uio->uio_rw = UIO_WRITE;
|
|
uio->uio_iovcnt = i;
|
|
uio->uio_resid = size;
|
|
|
|
pdu->disp = PDUDISP_SIGNAL;
|
|
pdu->flags = PDUF_BUSY | PDUF_NOUPDATE;
|
|
|
|
CS_BEGIN;
|
|
/* Enqueue for sending */
|
|
if (pdu->pdu.Opcode & OP_IMMEDIATE)
|
|
TAILQ_INSERT_HEAD(&conn->pdus_to_send, pdu, send_chain);
|
|
else
|
|
TAILQ_INSERT_TAIL(&conn->pdus_to_send, pdu, send_chain);
|
|
|
|
wakeup(&conn->pdus_to_send);
|
|
tsleep(pdu, PINOD, "test_send_pdu", 0);
|
|
CS_END;
|
|
unmap_databuf(p, pdu_ptr, psize);
|
|
par->status = ISCSI_STATUS_SUCCESS;
|
|
if (par->options & ISCSITEST_KILL_CONNECTION)
|
|
kill_connection(conn, ISCSI_STATUS_TEST_CONNECTION_CLOSED, NO_LOGOUT,
|
|
TRUE);
|
|
}
|
|
|
|
#endif
|