freebsd-dev/lib/libiscsiutil/text.c

Ignoring revisions in .git-blame-ignore-revs. Click here to bypass and see the normal blame view.

334 lines
10 KiB
C
Raw Normal View History

/*-
* SPDX-License-Identifier: BSD-2-Clause-FreeBSD
*
* Copyright (c) 2012 The FreeBSD Foundation
*
* This software was developed by Edward Tomasz Napierala under sponsorship
* from the FreeBSD Foundation.
*
* 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 AUTHOR 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 AUTHOR 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 <sys/types.h>
#include <netinet/in.h>
#include <stdlib.h>
#include <string.h>
#include <iscsi_proto.h>
#include "libiscsiutil.h"
/* Construct a new TextRequest PDU. */
static struct pdu *
text_new_request(struct connection *conn, uint32_t ttt)
{
struct pdu *request;
struct iscsi_bhs_text_request *bhstr;
request = pdu_new(conn);
bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
bhstr->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_REQUEST |
ISCSI_BHS_OPCODE_IMMEDIATE;
bhstr->bhstr_flags = BHSTR_FLAGS_FINAL;
bhstr->bhstr_initiator_task_tag = 0;
bhstr->bhstr_target_transfer_tag = ttt;
bhstr->bhstr_cmdsn = conn->conn_cmdsn;
bhstr->bhstr_expstatsn = htonl(conn->conn_statsn + 1);
return (request);
}
/* Receive a TextRequest PDU from a connection. */
static struct pdu *
text_receive_request(struct connection *conn)
{
struct pdu *request;
struct iscsi_bhs_text_request *bhstr;
request = pdu_new(conn);
pdu_receive(request);
if ((request->pdu_bhs->bhs_opcode & ~ISCSI_BHS_OPCODE_IMMEDIATE) !=
ISCSI_BHS_OPCODE_TEXT_REQUEST)
log_errx(1, "protocol error: received invalid opcode 0x%x",
request->pdu_bhs->bhs_opcode);
bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
/*
* XXX: Implement the C flag some day.
*/
if ((bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE)) !=
BHSTR_FLAGS_FINAL)
log_errx(1, "received TextRequest PDU with invalid "
"flags: %u", bhstr->bhstr_flags);
if (ISCSI_SNLT(ntohl(bhstr->bhstr_cmdsn), conn->conn_cmdsn)) {
log_errx(1, "received TextRequest PDU with decreasing CmdSN: "
"was %u, is %u", conn->conn_cmdsn, ntohl(bhstr->bhstr_cmdsn));
}
conn->conn_cmdsn = ntohl(bhstr->bhstr_cmdsn);
if ((bhstr->bhstr_opcode & ISCSI_BHS_OPCODE_IMMEDIATE) == 0)
conn->conn_cmdsn++;
return (request);
}
/* Construct a new TextResponse PDU in reply to a request. */
static struct pdu *
text_new_response(struct pdu *request, uint32_t ttt, bool final)
{
struct pdu *response;
struct connection *conn;
struct iscsi_bhs_text_request *bhstr;
struct iscsi_bhs_text_response *bhstr2;
bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
conn = request->pdu_connection;
response = pdu_new_response(request);
bhstr2 = (struct iscsi_bhs_text_response *)response->pdu_bhs;
bhstr2->bhstr_opcode = ISCSI_BHS_OPCODE_TEXT_RESPONSE;
if (final)
bhstr2->bhstr_flags = BHSTR_FLAGS_FINAL;
else
bhstr2->bhstr_flags = BHSTR_FLAGS_CONTINUE;
bhstr2->bhstr_lun = bhstr->bhstr_lun;
bhstr2->bhstr_initiator_task_tag = bhstr->bhstr_initiator_task_tag;
bhstr2->bhstr_target_transfer_tag = ttt;
bhstr2->bhstr_statsn = htonl(conn->conn_statsn++);
bhstr2->bhstr_expcmdsn = htonl(conn->conn_cmdsn);
bhstr2->bhstr_maxcmdsn = htonl(conn->conn_cmdsn);
return (response);
}
/* Receive a TextResponse PDU from a connection. */
static struct pdu *
text_receive_response(struct connection *conn)
{
struct pdu *response;
struct iscsi_bhs_text_response *bhstr;
uint8_t flags;
response = pdu_new(conn);
pdu_receive(response);
if (response->pdu_bhs->bhs_opcode != ISCSI_BHS_OPCODE_TEXT_RESPONSE)
log_errx(1, "protocol error: received invalid opcode 0x%x",
response->pdu_bhs->bhs_opcode);
bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
flags = bhstr->bhstr_flags & (BHSTR_FLAGS_FINAL | BHSTR_FLAGS_CONTINUE);
switch (flags) {
case BHSTR_FLAGS_CONTINUE:
if (bhstr->bhstr_target_transfer_tag == 0xffffffff)
log_errx(1, "received continue TextResponse PDU with "
"invalid TTT 0x%x",
bhstr->bhstr_target_transfer_tag);
break;
case BHSTR_FLAGS_FINAL:
if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
log_errx(1, "received final TextResponse PDU with "
"invalid TTT 0x%x",
bhstr->bhstr_target_transfer_tag);
break;
default:
log_errx(1, "received TextResponse PDU with invalid "
"flags: %u", bhstr->bhstr_flags);
}
if (ntohl(bhstr->bhstr_statsn) != conn->conn_statsn + 1) {
log_errx(1, "received TextResponse PDU with wrong StatSN: "
"is %u, should be %u", ntohl(bhstr->bhstr_statsn),
conn->conn_statsn + 1);
}
conn->conn_statsn = ntohl(bhstr->bhstr_statsn);
return (response);
}
/*
* Send a list of keys from the initiator to the target in a
* TextRequest PDU.
*/
void
text_send_request(struct connection *conn, struct keys *request_keys)
{
struct pdu *request;
request = text_new_request(conn, 0xffffffff);
keys_save_pdu(request_keys, request);
if (request->pdu_data_len == 0)
log_errx(1, "No keys to send in a TextRequest");
if (request->pdu_data_len >
(size_t)conn->conn_max_send_data_segment_length)
log_errx(1, "Keys to send in TextRequest are too long");
pdu_send(request);
pdu_delete(request);
}
/*
* Read a list of keys from the target in a series of TextResponse
* PDUs.
*/
struct keys *
text_read_response(struct connection *conn)
{
struct keys *response_keys;
char *keys_data;
size_t keys_len;
uint32_t ttt;
keys_data = NULL;
keys_len = 0;
ttt = 0xffffffff;
for (;;) {
struct pdu *request, *response;
struct iscsi_bhs_text_response *bhstr;
response = text_receive_response(conn);
bhstr = (struct iscsi_bhs_text_response *)response->pdu_bhs;
if (keys_data == NULL) {
ttt = bhstr->bhstr_target_transfer_tag;
keys_data = response->pdu_data;
keys_len = response->pdu_data_len;
response->pdu_data = NULL;
} else {
keys_data = realloc(keys_data,
keys_len + response->pdu_data_len);
if (keys_data == NULL)
log_err(1, "failed to grow keys block");
memcpy(keys_data + keys_len, response->pdu_data,
response->pdu_data_len);
keys_len += response->pdu_data_len;
}
if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) != 0) {
pdu_delete(response);
break;
}
if (bhstr->bhstr_target_transfer_tag != ttt)
log_errx(1, "received non-final TextRequest PDU with "
"invalid TTT 0x%x",
bhstr->bhstr_target_transfer_tag);
pdu_delete(response);
/* Send an empty request. */
request = text_new_request(conn, ttt);
pdu_send(request);
pdu_delete(request);
}
response_keys = keys_new();
keys_load(response_keys, keys_data, keys_len);
free(keys_data);
return (response_keys);
}
/*
* Read a list of keys from the initiator in a TextRequest PDU.
*/
struct keys *
text_read_request(struct connection *conn, struct pdu **requestp)
{
struct iscsi_bhs_text_request *bhstr;
struct pdu *request;
struct keys *request_keys;
request = text_receive_request(conn);
bhstr = (struct iscsi_bhs_text_request *)request->pdu_bhs;
if (bhstr->bhstr_target_transfer_tag != 0xffffffff)
log_errx(1, "received TextRequest PDU with invalid TTT 0x%x",
bhstr->bhstr_target_transfer_tag);
if (ntohl(bhstr->bhstr_expstatsn) != conn->conn_statsn) {
log_errx(1, "received TextRequest PDU with wrong ExpStatSN: "
"is %u, should be %u", ntohl(bhstr->bhstr_expstatsn),
conn->conn_statsn);
}
request_keys = keys_new();
keys_load_pdu(request_keys, request);
*requestp = request;
return (request_keys);
}
/*
* Send a response back to the initiator as a series of TextResponse
* PDUs.
*/
void
text_send_response(struct pdu *request, struct keys *response_keys)
{
struct connection *conn = request->pdu_connection;
char *keys_data;
size_t keys_len;
size_t keys_offset;
uint32_t ttt;
keys_save(response_keys, &keys_data, &keys_len);
keys_offset = 0;
ttt = keys_len;
for (;;) {
struct pdu *request2, *response;
struct iscsi_bhs_text_request *bhstr;
size_t todo;
bool final;
todo = keys_len - keys_offset;
if (todo > (size_t)conn->conn_max_send_data_segment_length) {
final = false;
todo = conn->conn_max_send_data_segment_length;
} else {
final = true;
ttt = 0xffffffff;
}
response = text_new_response(request, ttt, final);
response->pdu_data = keys_data + keys_offset;
response->pdu_data_len = todo;
keys_offset += todo;
pdu_send(response);
response->pdu_data = NULL;
pdu_delete(response);
if (final)
break;
/*
* Wait for an empty request.
*
* XXX: Linux's Open-iSCSI initiator doesn't update
* ExpStatSN when receiving a TextResponse PDU.
*/
request2 = text_receive_request(conn);
bhstr = (struct iscsi_bhs_text_request *)request2->pdu_bhs;
if ((bhstr->bhstr_flags & BHSTR_FLAGS_FINAL) == 0)
log_errx(1, "received continuation TextRequest PDU "
"without F set");
if (pdu_data_segment_length(request2) != 0)
log_errx(1, "received non-empty continuation "
"TextRequest PDU");
if (bhstr->bhstr_target_transfer_tag != ttt)
log_errx(1, "received TextRequest PDU with invalid "
"TTT 0x%x", bhstr->bhstr_target_transfer_tag);
pdu_delete(request2);
}
free(keys_data);
}